@canonry/canonry 7.9.1 → 7.11.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.
- package/assets/agent-workspace/skills/aero/references/reporting.md +2 -0
- package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +1 -1
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +30 -4
- package/assets/assets/{AuditHistoryPanel-D0nukTwD.js → AuditHistoryPanel-D1WdkJKU.js} +1 -1
- package/assets/assets/{BacklinksPage-B525ij0g.js → BacklinksPage-Uf1djxaV.js} +1 -1
- package/assets/assets/{HistoryPage-DvupqQDM.js → HistoryPage-CZPxzlS_.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-CwDbi1XN.js → MeasurementPropertyPage-CaWq1f2G.js} +1 -1
- package/assets/assets/{ProjectPage-DmZZ2C7q.js → ProjectPage-BimakE9X.js} +7 -7
- package/assets/assets/{RunsPage-DHTvHcGm.js → RunsPage-CDX5RxHi.js} +1 -1
- package/assets/assets/{SettingsPage-BwAFzB7A.js → SettingsPage-BfzKP-IR.js} +1 -1
- package/assets/assets/SiteHealthSection-42_E1ueq.js +4 -0
- package/assets/assets/{TrafficPage-BOcXASAM.js → TrafficPage-RjkpfGMw.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-DTq0cN6g.js → TrafficSourceDetailPage-DIWDPo-8.js} +1 -1
- package/assets/assets/{extract-error-message-BOTDI6g5.js → extract-error-message-D_e8B2s_.js} +1 -1
- package/assets/assets/index-BNeiohKS.css +1 -0
- package/assets/assets/{index-BLJfGYUw.js → index-DKl_jsi3.js} +24 -24
- package/assets/assets/{react-sigma_core.esm.min-BEBdJNtD.js → react-sigma_core.esm.min-Br-N5hoB.js} +1 -1
- package/assets/assets/{v2-overview-adapter-BPZ-H3VY.js → v2-overview-adapter-BCSXgy2l.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-ZLB7WX5D.js → chunk-7G7YVTDA.js} +56 -6
- package/dist/{chunk-DO7XPDG5.js → chunk-CF7G2HGX.js} +5790 -4451
- package/dist/{chunk-LO56QXPO.js → chunk-GQAESNJT.js} +90 -2
- package/dist/{chunk-KRFTA4I7.js → chunk-HA2BAJ6Z.js} +3507 -2290
- package/dist/{chunk-AAMKTL6B.js → chunk-JRBQFRNT.js} +3 -3
- package/dist/{chunk-T2V2PESH.js → chunk-OH7E7TJD.js} +2 -2
- package/dist/{chunk-4BKRZGLC.js → chunk-S46DWUNR.js} +706 -245
- package/dist/cli.js +166 -38
- package/dist/{demo-server-KDMG3APY.js → demo-server-BYLZFDKI.js} +4 -4
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-LAXJFE3U.js → intelligence-service-TIM4XTYH.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +10 -10
- package/assets/assets/SiteHealthSection-hi54eSIt.js +0 -4
- package/assets/assets/index-Rcbm8bK1.css +0 -1
|
@@ -20,20 +20,20 @@ import {
|
|
|
20
20
|
saveConfig,
|
|
21
21
|
saveConfigPatch,
|
|
22
22
|
sentimentInstallReadiness
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-GQAESNJT.js";
|
|
24
24
|
import {
|
|
25
25
|
computeFactorAverages,
|
|
26
26
|
deleteSiteCrawlGraphLayout,
|
|
27
27
|
persistSiteCrawlGraphLayout,
|
|
28
28
|
prepareSiteCrawlGraphLayout,
|
|
29
29
|
recordedSharePct
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-OH7E7TJD.js";
|
|
31
31
|
import {
|
|
32
32
|
CanonryMcpToolNames,
|
|
33
33
|
CliError,
|
|
34
34
|
PACKAGE_VERSION,
|
|
35
35
|
canonryMcpTools
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-7G7YVTDA.js";
|
|
37
37
|
import {
|
|
38
38
|
CC_CACHE_DIR,
|
|
39
39
|
DUCKDB_SPEC,
|
|
@@ -58,12 +58,16 @@ import {
|
|
|
58
58
|
agentMemory,
|
|
59
59
|
agentSessions,
|
|
60
60
|
agentToolEvents,
|
|
61
|
+
answerIdentityChanged,
|
|
62
|
+
answerIdentityFrom,
|
|
61
63
|
anyUsersExist,
|
|
62
64
|
apiKeys,
|
|
63
65
|
apiRoutes,
|
|
66
|
+
applyCompetitorAutoAliases,
|
|
64
67
|
assessConversionTrackingIntegrity,
|
|
65
68
|
auditLog,
|
|
66
69
|
backfillProjectAnswerMentions,
|
|
70
|
+
backfillProjectAnswerMentionsInChunks,
|
|
67
71
|
backlinkDomains,
|
|
68
72
|
backlinkSummaries,
|
|
69
73
|
bingCoverageSnapshots,
|
|
@@ -74,6 +78,7 @@ import {
|
|
|
74
78
|
ccReleasePaths,
|
|
75
79
|
ccReleaseSyncs,
|
|
76
80
|
classifyReviewsError,
|
|
81
|
+
competitorIdentityColumns,
|
|
77
82
|
competitors,
|
|
78
83
|
computeCitedCompetitorDomains,
|
|
79
84
|
countAttributes,
|
|
@@ -81,6 +86,7 @@ import {
|
|
|
81
86
|
createCredentialChecker,
|
|
82
87
|
createFastifyLogger,
|
|
83
88
|
createLogger,
|
|
89
|
+
createProjectPassQueue,
|
|
84
90
|
createRunCompetitorResolver,
|
|
85
91
|
createUserSession,
|
|
86
92
|
deliverWebhook,
|
|
@@ -156,6 +162,7 @@ import {
|
|
|
156
162
|
notifications,
|
|
157
163
|
parseCookieHeader,
|
|
158
164
|
parseJsonColumn,
|
|
165
|
+
previewCompetitorAutoAliases,
|
|
159
166
|
probeLatestRelease,
|
|
160
167
|
projects,
|
|
161
168
|
providerBatchRequests,
|
|
@@ -165,7 +172,9 @@ import {
|
|
|
165
172
|
queryBacklinks,
|
|
166
173
|
querySnapshots,
|
|
167
174
|
queueRunIfProjectIdle,
|
|
175
|
+
readAnswerIdentity,
|
|
168
176
|
readInstalledVersion,
|
|
177
|
+
readMarketCompetitorNames,
|
|
169
178
|
readNegativeReviewMaxStars,
|
|
170
179
|
recordSentimentCompletion,
|
|
171
180
|
redactNotificationUrl,
|
|
@@ -205,7 +214,7 @@ import {
|
|
|
205
214
|
siteLivenessState,
|
|
206
215
|
toAlertView,
|
|
207
216
|
usageCounters
|
|
208
|
-
} from "./chunk-
|
|
217
|
+
} from "./chunk-HA2BAJ6Z.js";
|
|
209
218
|
import {
|
|
210
219
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
211
220
|
AGENT_NONE,
|
|
@@ -215,12 +224,14 @@ import {
|
|
|
215
224
|
AdsAdGroupBillingEventTypes,
|
|
216
225
|
AdsCampaignBiddingTypes,
|
|
217
226
|
AgentProviderIds,
|
|
227
|
+
AnchoredSpanSources,
|
|
218
228
|
AppError,
|
|
219
229
|
BacklinkSources,
|
|
220
230
|
CANONRY_NPM_PACKAGE_URL,
|
|
221
231
|
CITED_URL_CAPTURE_VERSION,
|
|
222
232
|
CcReleaseSyncStatuses,
|
|
223
233
|
CheckNotificationPolicies,
|
|
234
|
+
DEFAULT_COMPETITOR_AUTO_ALIAS_MODE,
|
|
224
235
|
DEFAULT_VIEWER_RESEARCH_DAILY_RUN_LIMIT,
|
|
225
236
|
DiscoveryCompetitorTypes,
|
|
226
237
|
FEEDBACK_LIMITS,
|
|
@@ -247,6 +258,7 @@ import {
|
|
|
247
258
|
ProviderBatchStatuses,
|
|
248
259
|
ProviderBatchSubmitError,
|
|
249
260
|
ProviderDispatchModes,
|
|
261
|
+
ProviderNames,
|
|
250
262
|
RATIO_UNIT_META_KEY,
|
|
251
263
|
ROUTE_CAPABLE_CITED_URL_PROVIDERS,
|
|
252
264
|
RUN_FILL_PROVIDER_BREAKER,
|
|
@@ -298,6 +310,7 @@ import {
|
|
|
298
310
|
classifyTemplateLinkEdge,
|
|
299
311
|
cloudflareQueueNameSchema,
|
|
300
312
|
compareSemver,
|
|
313
|
+
competitorIdentityAliases,
|
|
301
314
|
competitorLabelFromDomain,
|
|
302
315
|
compileBrandAliases,
|
|
303
316
|
computeBacklinkSummaryMetrics,
|
|
@@ -364,7 +377,6 @@ import {
|
|
|
364
377
|
missingDependency,
|
|
365
378
|
nextScheduleUpdatedAt,
|
|
366
379
|
normalizeAgentSlug,
|
|
367
|
-
normalizeCompetitorAliases,
|
|
368
380
|
normalizeIdTokens,
|
|
369
381
|
normalizeMeasurementExecutionQueryText,
|
|
370
382
|
normalizeProjectDomain,
|
|
@@ -414,7 +426,7 @@ import {
|
|
|
414
426
|
visibilityReportRequestSchema,
|
|
415
427
|
winnabilityClassLabel,
|
|
416
428
|
withRetry
|
|
417
|
-
} from "./chunk-
|
|
429
|
+
} from "./chunk-CF7G2HGX.js";
|
|
418
430
|
|
|
419
431
|
// src/runtime-context.ts
|
|
420
432
|
function cliRuntimeContext(env = process.env, stdio = process) {
|
|
@@ -3545,7 +3557,8 @@ import fs9 from "fs";
|
|
|
3545
3557
|
import path8 from "path";
|
|
3546
3558
|
import { isIP } from "net";
|
|
3547
3559
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3548
|
-
import {
|
|
3560
|
+
import { setImmediate as yieldToEventLoop4 } from "timers/promises";
|
|
3561
|
+
import { and as and23, eq as eq29, isNotNull as isNotNull4, sql as sql11 } from "drizzle-orm";
|
|
3549
3562
|
|
|
3550
3563
|
// ../integration-typesafe/src/classifier.ts
|
|
3551
3564
|
import { createHash } from "crypto";
|
|
@@ -4519,6 +4532,42 @@ var geminiAdapter = {
|
|
|
4519
4532
|
}
|
|
4520
4533
|
};
|
|
4521
4534
|
|
|
4535
|
+
// ../provider-gemini/src/anchored-spans.ts
|
|
4536
|
+
function isRecord2(value) {
|
|
4537
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4538
|
+
}
|
|
4539
|
+
function chunkSource(chunk) {
|
|
4540
|
+
if (!isRecord2(chunk) || !isRecord2(chunk.web)) return null;
|
|
4541
|
+
const uri = typeof chunk.web.uri === "string" ? chunk.web.uri : "";
|
|
4542
|
+
const uriHost = hostOf(uri);
|
|
4543
|
+
if (uriHost && uriHost !== VERTEX_AI_SEARCH_PROXY_DOMAIN && registrableDomain(uriHost)) return uri;
|
|
4544
|
+
const title = typeof chunk.web.title === "string" ? chunk.web.title.trim() : "";
|
|
4545
|
+
return title && registrableDomain(title) ? title : null;
|
|
4546
|
+
}
|
|
4547
|
+
function extractAnchoredSpans(rawResponse) {
|
|
4548
|
+
const candidates = Array.isArray(rawResponse.candidates) ? rawResponse.candidates : [];
|
|
4549
|
+
const candidate = candidates[0];
|
|
4550
|
+
if (!isRecord2(candidate) || !isRecord2(candidate.groundingMetadata)) return [];
|
|
4551
|
+
const metadata2 = candidate.groundingMetadata;
|
|
4552
|
+
const chunks = Array.isArray(metadata2.groundingChunks) ? metadata2.groundingChunks : [];
|
|
4553
|
+
const spans = [];
|
|
4554
|
+
for (const support of Array.isArray(metadata2.groundingSupports) ? metadata2.groundingSupports : []) {
|
|
4555
|
+
if (!isRecord2(support) || !isRecord2(support.segment)) continue;
|
|
4556
|
+
const text = support.segment.text;
|
|
4557
|
+
if (typeof text !== "string" || !text.trim()) continue;
|
|
4558
|
+
const sources = /* @__PURE__ */ new Set();
|
|
4559
|
+
for (const index of Array.isArray(support.groundingChunkIndices) ? support.groundingChunkIndices : []) {
|
|
4560
|
+
if (typeof index !== "number" || !Number.isInteger(index) || index < 0 || index >= chunks.length) continue;
|
|
4561
|
+
const source = chunkSource(chunks[index]);
|
|
4562
|
+
if (source) sources.add(source);
|
|
4563
|
+
}
|
|
4564
|
+
for (const source of sources) {
|
|
4565
|
+
spans.push({ text, source, kind: "window", via: AnchoredSpanSources["gemini-support"] });
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
return spans;
|
|
4569
|
+
}
|
|
4570
|
+
|
|
4522
4571
|
// ../provider-openai/src/normalize.ts
|
|
4523
4572
|
import OpenAI from "openai";
|
|
4524
4573
|
|
|
@@ -4961,6 +5010,40 @@ var openaiAdapter = {
|
|
|
4961
5010
|
}
|
|
4962
5011
|
};
|
|
4963
5012
|
|
|
5013
|
+
// ../provider-openai/src/anchored-spans.ts
|
|
5014
|
+
function isRecord3(value) {
|
|
5015
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5016
|
+
}
|
|
5017
|
+
function extractAnchoredSpans2(rawResponse) {
|
|
5018
|
+
const spans = [];
|
|
5019
|
+
const output = Array.isArray(rawResponse.output) ? rawResponse.output : [];
|
|
5020
|
+
for (const item of output) {
|
|
5021
|
+
if (!isRecord3(item) || item.type !== "message" || !Array.isArray(item.content)) continue;
|
|
5022
|
+
for (const part of item.content) {
|
|
5023
|
+
if (!isRecord3(part) || part.type !== "output_text" || typeof part.text !== "string") continue;
|
|
5024
|
+
const text = part.text;
|
|
5025
|
+
const citations = (Array.isArray(part.annotations) ? part.annotations : []).flatMap((annotation) => {
|
|
5026
|
+
if (!isRecord3(annotation) || annotation.type !== "url_citation" || typeof annotation.url !== "string") return [];
|
|
5027
|
+
const start = annotation.start_index;
|
|
5028
|
+
const end = annotation.end_index;
|
|
5029
|
+
if (typeof start !== "number" || typeof end !== "number" || !Number.isInteger(start) || !Number.isInteger(end)) return [];
|
|
5030
|
+
if (start < 0 || end < start || end > text.length) return [];
|
|
5031
|
+
return [{ start, end, url: annotation.url }];
|
|
5032
|
+
}).sort((a, b) => a.start - b.start);
|
|
5033
|
+
let previousEnd = 0;
|
|
5034
|
+
for (const citation of citations) {
|
|
5035
|
+
const lineStart = citation.start === 0 ? 0 : text.lastIndexOf("\n", citation.start - 1) + 1;
|
|
5036
|
+
const from = Math.max(lineStart, previousEnd);
|
|
5037
|
+
if (citation.start > from) {
|
|
5038
|
+
spans.push({ text: text.slice(from, citation.start), source: citation.url, kind: "window", via: AnchoredSpanSources["openai-annotation"] });
|
|
5039
|
+
}
|
|
5040
|
+
previousEnd = Math.max(previousEnd, citation.end);
|
|
5041
|
+
}
|
|
5042
|
+
}
|
|
5043
|
+
}
|
|
5044
|
+
return spans;
|
|
5045
|
+
}
|
|
5046
|
+
|
|
4964
5047
|
// ../provider-claude/src/normalize.ts
|
|
4965
5048
|
import Anthropic from "@anthropic-ai/sdk";
|
|
4966
5049
|
|
|
@@ -5597,6 +5680,40 @@ var claudeAdapter = {
|
|
|
5597
5680
|
}
|
|
5598
5681
|
};
|
|
5599
5682
|
|
|
5683
|
+
// ../provider-claude/src/anchored-spans.ts
|
|
5684
|
+
function isRecord4(value) {
|
|
5685
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5686
|
+
}
|
|
5687
|
+
function leadIn(text) {
|
|
5688
|
+
const end = text.trimEnd().length;
|
|
5689
|
+
if (end === 0) return "";
|
|
5690
|
+
return text.slice(text.lastIndexOf("\n", end - 1) + 1);
|
|
5691
|
+
}
|
|
5692
|
+
function extractAnchoredSpans3(rawResponse) {
|
|
5693
|
+
const spans = [];
|
|
5694
|
+
const content = Array.isArray(rawResponse.content) ? rawResponse.content : [];
|
|
5695
|
+
let previousUncited = null;
|
|
5696
|
+
for (const block of content) {
|
|
5697
|
+
if (!isRecord4(block) || block.type !== "text" || typeof block.text !== "string") continue;
|
|
5698
|
+
const urls = /* @__PURE__ */ new Set();
|
|
5699
|
+
for (const citation of Array.isArray(block.citations) ? block.citations : []) {
|
|
5700
|
+
if (isRecord4(citation) && citation.type === "web_search_result_location" && typeof citation.url === "string") {
|
|
5701
|
+
urls.add(citation.url);
|
|
5702
|
+
}
|
|
5703
|
+
}
|
|
5704
|
+
if (urls.size === 0) {
|
|
5705
|
+
previousUncited = block.text;
|
|
5706
|
+
continue;
|
|
5707
|
+
}
|
|
5708
|
+
const window = (previousUncited === null ? "" : leadIn(previousUncited)) + block.text;
|
|
5709
|
+
for (const url of urls) {
|
|
5710
|
+
spans.push({ text: window, source: url, kind: "window", via: AnchoredSpanSources["claude-citation"] });
|
|
5711
|
+
}
|
|
5712
|
+
previousUncited = null;
|
|
5713
|
+
}
|
|
5714
|
+
return spans;
|
|
5715
|
+
}
|
|
5716
|
+
|
|
5600
5717
|
// ../provider-local/src/normalize.ts
|
|
5601
5718
|
import OpenAI3 from "openai";
|
|
5602
5719
|
|
|
@@ -6631,6 +6748,54 @@ function reparseStoredResult4(rawResponse) {
|
|
|
6631
6748
|
if (agent) return parseAgentResponse(agent);
|
|
6632
6749
|
return parseSonarResponse(rawResponse);
|
|
6633
6750
|
}
|
|
6751
|
+
function extractAnchoredSpans4(rawResponse) {
|
|
6752
|
+
const agent = agentResponseOf(rawResponse);
|
|
6753
|
+
let text;
|
|
6754
|
+
let sourceFor;
|
|
6755
|
+
if (agent) {
|
|
6756
|
+
text = extractAgentAnswerText(agent);
|
|
6757
|
+
const byId = /* @__PURE__ */ new Map();
|
|
6758
|
+
for (const item of agentOutput(agent)) {
|
|
6759
|
+
if (item.type !== "search_results" || !Array.isArray(item.results)) continue;
|
|
6760
|
+
for (const result2 of item.results) {
|
|
6761
|
+
if (!isRecord5(result2) || typeof result2.id !== "number" || typeof result2.url !== "string" || !result2.url) continue;
|
|
6762
|
+
if (!byId.has(result2.id)) byId.set(result2.id, result2.url);
|
|
6763
|
+
}
|
|
6764
|
+
}
|
|
6765
|
+
sourceFor = (marker) => byId.get(marker);
|
|
6766
|
+
} else {
|
|
6767
|
+
text = extractAnswerText3(rawResponse);
|
|
6768
|
+
const citations = extractCitations(rawResponse);
|
|
6769
|
+
sourceFor = (marker) => citations[marker - 1];
|
|
6770
|
+
}
|
|
6771
|
+
const spans = [];
|
|
6772
|
+
if (!text.includes("[")) return spans;
|
|
6773
|
+
let groupStart = -1;
|
|
6774
|
+
let groupEnd = -1;
|
|
6775
|
+
let previousEnd = 0;
|
|
6776
|
+
let markers = [];
|
|
6777
|
+
const flush = () => {
|
|
6778
|
+
if (markers.length === 0) return;
|
|
6779
|
+
const lineStart = groupStart === 0 ? 0 : text.lastIndexOf("\n", groupStart - 1) + 1;
|
|
6780
|
+
const from = Math.max(lineStart, previousEnd);
|
|
6781
|
+
const window = text.slice(from, groupStart);
|
|
6782
|
+
if (window.trim()) {
|
|
6783
|
+
for (const source of new Set(markers.map(sourceFor).filter((url) => typeof url === "string" && url.length > 0))) {
|
|
6784
|
+
spans.push({ text: window, source, kind: "window", via: AnchoredSpanSources["perplexity-marker"] });
|
|
6785
|
+
}
|
|
6786
|
+
}
|
|
6787
|
+
previousEnd = groupEnd;
|
|
6788
|
+
markers = [];
|
|
6789
|
+
};
|
|
6790
|
+
for (const match of text.matchAll(/\[(\d{1,3})\]/g)) {
|
|
6791
|
+
if (markers.length > 0 && match.index !== groupEnd) flush();
|
|
6792
|
+
if (markers.length === 0) groupStart = match.index;
|
|
6793
|
+
markers.push(Number(match[1]));
|
|
6794
|
+
groupEnd = match.index + match[0].length;
|
|
6795
|
+
}
|
|
6796
|
+
flush();
|
|
6797
|
+
return spans;
|
|
6798
|
+
}
|
|
6634
6799
|
function createClient3(apiKey) {
|
|
6635
6800
|
return new OpenAI4({ apiKey, baseURL: BASE_URL });
|
|
6636
6801
|
}
|
|
@@ -6642,11 +6807,11 @@ function assertUsableResponse(response) {
|
|
|
6642
6807
|
if (status === void 0 || status === "completed") return;
|
|
6643
6808
|
if (status === "incomplete") {
|
|
6644
6809
|
if (extractAgentAnswerText(response).trim().length > 0) return;
|
|
6645
|
-
const details =
|
|
6810
|
+
const details = isRecord5(response.incomplete_details) ? response.incomplete_details : void 0;
|
|
6646
6811
|
const reason = typeof details?.reason === "string" && details.reason.length > 0 ? details.reason : "no reason given";
|
|
6647
6812
|
throw new Error(`agent response incomplete with no answer: ${reason}`);
|
|
6648
6813
|
}
|
|
6649
|
-
const error =
|
|
6814
|
+
const error = isRecord5(response.error) ? response.error : void 0;
|
|
6650
6815
|
const message = typeof error?.message === "string" && error.message.length > 0 ? error.message : "no error detail";
|
|
6651
6816
|
const type = typeof error?.type === "string" && error.type.length > 0 ? ` (${error.type})` : "";
|
|
6652
6817
|
throw new Error(`agent response ${typeof status === "string" ? status : describeError(status)}: ${message}${type}`);
|
|
@@ -6657,7 +6822,7 @@ function agentResponseOf(rawResponse) {
|
|
|
6657
6822
|
return nested && Array.isArray(nested.output) ? nested : null;
|
|
6658
6823
|
}
|
|
6659
6824
|
function agentOutput(response) {
|
|
6660
|
-
return Array.isArray(response.output) ? response.output.filter(
|
|
6825
|
+
return Array.isArray(response.output) ? response.output.filter(isRecord5) : [];
|
|
6661
6826
|
}
|
|
6662
6827
|
function parseAgentResponse(response) {
|
|
6663
6828
|
const groundingSources = extractAgentGroundingSources(response);
|
|
@@ -6675,7 +6840,7 @@ function extractAgentAnswerText(response) {
|
|
|
6675
6840
|
for (const item of agentOutput(response)) {
|
|
6676
6841
|
if (item.type !== "message" || !Array.isArray(item.content)) continue;
|
|
6677
6842
|
for (const part of item.content) {
|
|
6678
|
-
if (
|
|
6843
|
+
if (isRecord5(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
|
|
6679
6844
|
}
|
|
6680
6845
|
}
|
|
6681
6846
|
return parts.join("");
|
|
@@ -6684,7 +6849,7 @@ function extractAgentGroundingSources(response) {
|
|
|
6684
6849
|
const sources = [];
|
|
6685
6850
|
const seen = /* @__PURE__ */ new Set();
|
|
6686
6851
|
const add = (entry) => {
|
|
6687
|
-
if (!
|
|
6852
|
+
if (!isRecord5(entry) || typeof entry.url !== "string" || entry.url.length === 0) return;
|
|
6688
6853
|
if (seen.has(entry.url)) return;
|
|
6689
6854
|
seen.add(entry.url);
|
|
6690
6855
|
sources.push({ uri: entry.url, title: typeof entry.title === "string" ? entry.title : "" });
|
|
@@ -6696,7 +6861,7 @@ function extractAgentGroundingSources(response) {
|
|
|
6696
6861
|
item.contents.forEach(add);
|
|
6697
6862
|
} else if (item.type === "message" && Array.isArray(item.content)) {
|
|
6698
6863
|
for (const part of item.content) {
|
|
6699
|
-
if (
|
|
6864
|
+
if (isRecord5(part) && Array.isArray(part.annotations)) part.annotations.forEach(add);
|
|
6700
6865
|
}
|
|
6701
6866
|
}
|
|
6702
6867
|
}
|
|
@@ -6713,13 +6878,13 @@ function extractAgentSearchQueries(response) {
|
|
|
6713
6878
|
return [...queries2];
|
|
6714
6879
|
}
|
|
6715
6880
|
function extractAgentUsage(response) {
|
|
6716
|
-
const usage =
|
|
6881
|
+
const usage = isRecord5(response.usage) ? response.usage : void 0;
|
|
6717
6882
|
if (!usage) return void 0;
|
|
6718
|
-
const details =
|
|
6883
|
+
const details = isRecord5(usage.input_tokens_details) ? usage.input_tokens_details : void 0;
|
|
6719
6884
|
const cachedInputTokens = usageCount(details?.cache_read_input_tokens);
|
|
6720
6885
|
const cacheWriteTokens = usageCount(details?.cache_creation_input_tokens);
|
|
6721
|
-
const toolCalls =
|
|
6722
|
-
const webSearch =
|
|
6886
|
+
const toolCalls = isRecord5(usage.tool_calls_details) ? usage.tool_calls_details : void 0;
|
|
6887
|
+
const webSearch = isRecord5(toolCalls?.web_search) ? toolCalls.web_search : void 0;
|
|
6723
6888
|
const searchCount = typeof webSearch?.invocation === "number" ? usageCount(webSearch.invocation) : agentOutput(response).filter((item) => item.type === "search_results").length;
|
|
6724
6889
|
return {
|
|
6725
6890
|
inputTokens: Math.max(0, usageCount(usage.input_tokens) - cachedInputTokens - cacheWriteTokens),
|
|
@@ -6730,7 +6895,7 @@ function extractAgentUsage(response) {
|
|
|
6730
6895
|
};
|
|
6731
6896
|
}
|
|
6732
6897
|
function extractAgentStopReason(response) {
|
|
6733
|
-
const details =
|
|
6898
|
+
const details = isRecord5(response.incomplete_details) ? response.incomplete_details : void 0;
|
|
6734
6899
|
if (typeof details?.reason === "string" && details.reason.length > 0) return details.reason;
|
|
6735
6900
|
return typeof response.status === "string" && response.status.length > 0 ? response.status : void 0;
|
|
6736
6901
|
}
|
|
@@ -6798,7 +6963,7 @@ function extractSearchResults(rawResponse) {
|
|
|
6798
6963
|
function parseSearchResultsArray(value) {
|
|
6799
6964
|
if (!Array.isArray(value)) return [];
|
|
6800
6965
|
return value.flatMap((result2) => {
|
|
6801
|
-
if (!
|
|
6966
|
+
if (!isRecord5(result2)) return [];
|
|
6802
6967
|
const url = result2.url;
|
|
6803
6968
|
if (typeof url !== "string" || url.length === 0) {
|
|
6804
6969
|
return [];
|
|
@@ -6825,9 +6990,9 @@ function extractAnswerText3(rawResponse) {
|
|
|
6825
6990
|
}
|
|
6826
6991
|
}
|
|
6827
6992
|
function extractNestedApiResponse(rawResponse) {
|
|
6828
|
-
return
|
|
6993
|
+
return isRecord5(rawResponse.apiResponse) ? rawResponse.apiResponse : null;
|
|
6829
6994
|
}
|
|
6830
|
-
function
|
|
6995
|
+
function isRecord5(value) {
|
|
6831
6996
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6832
6997
|
}
|
|
6833
6998
|
function extractCitedDomains2(groundingSources) {
|
|
@@ -7873,7 +8038,7 @@ function nonBlank(value) {
|
|
|
7873
8038
|
return trimmed ? trimmed : void 0;
|
|
7874
8039
|
}
|
|
7875
8040
|
function resolveBuildCommit(env = process.env) {
|
|
7876
|
-
const embedded = true ? "
|
|
8041
|
+
const embedded = true ? "df2022e0ce72be6d0666fd60f286bd9a08926e7b" : void 0;
|
|
7877
8042
|
return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
|
|
7878
8043
|
}
|
|
7879
8044
|
function resolveInstanceIdentity(env = process.env) {
|
|
@@ -8295,17 +8460,15 @@ function runOutcome(inserted, providerErrors, planShortfall) {
|
|
|
8295
8460
|
error: someFailed ? serializeRunError(buildProviderRunError(providerErrors)) : null
|
|
8296
8461
|
};
|
|
8297
8462
|
}
|
|
8298
|
-
function competitorIdentityKey(rows) {
|
|
8299
|
-
return JSON.stringify(rows.map((row) => ({ domain: row.domain, aliases: normalizeCompetitorAliases(row.aliases) })).sort((left, right) => left.domain.localeCompare(right.domain)));
|
|
8300
|
-
}
|
|
8301
8463
|
function runRecordingContext(db, input) {
|
|
8302
|
-
const
|
|
8464
|
+
const identity = answerIdentityFrom(input.project, input.competitors, input.marketNames);
|
|
8465
|
+
const resolveCompetitors = createRunCompetitorResolver(db, identity.competitors, identity.marketNames);
|
|
8303
8466
|
return {
|
|
8304
8467
|
runId: input.runId,
|
|
8305
|
-
|
|
8468
|
+
identity: identity.fingerprint,
|
|
8469
|
+
allDomains: identity.projectDomains,
|
|
8306
8470
|
competitorsFor: (executionId) => resolveCompetitors(input.measurementPlanVersionId, executionId),
|
|
8307
|
-
allBrandNames:
|
|
8308
|
-
competitorIdentity: competitorIdentityKey(input.competitors)
|
|
8471
|
+
allBrandNames: identity.projectBrandNames
|
|
8309
8472
|
};
|
|
8310
8473
|
}
|
|
8311
8474
|
function resolvePlanExecution(run, projectQueries) {
|
|
@@ -8574,13 +8737,14 @@ var JobRunner = class {
|
|
|
8574
8737
|
throw new Error("No providers configured. Add at least one provider API key.");
|
|
8575
8738
|
}
|
|
8576
8739
|
log2.info("run.dispatch", { runId, providerCount: activeProviders.length, providers: activeProviders.map((p) => p.adapter.name) });
|
|
8577
|
-
const projectCompetitors = this.db.select().from(competitors).where(eq3(competitors.projectId, projectId)).all();
|
|
8740
|
+
const projectCompetitors = this.db.select().from(competitors).where(eq3(competitors.projectId, projectId)).orderBy(competitors.domain).all();
|
|
8578
8741
|
const competitorDomains = projectCompetitors.map((c) => c.domain);
|
|
8579
8742
|
recording = runRecordingContext(this.db, {
|
|
8580
8743
|
runId,
|
|
8581
8744
|
measurementPlanVersionId: existingRun.measurementPlanVersionId,
|
|
8582
8745
|
project,
|
|
8583
|
-
competitors: projectCompetitors
|
|
8746
|
+
competitors: projectCompetitors,
|
|
8747
|
+
marketNames: readMarketCompetitorNames(this.db, projectId)
|
|
8584
8748
|
});
|
|
8585
8749
|
const { allDomains, allBrandNames } = recording;
|
|
8586
8750
|
const { aliases: competitorAliases } = recording.competitorsFor(null);
|
|
@@ -8808,14 +8972,17 @@ var JobRunner = class {
|
|
|
8808
8972
|
requestedModel: config.model ?? null
|
|
8809
8973
|
})),
|
|
8810
8974
|
// Freeze the exact identity we actually dispatched with (domain label
|
|
8811
|
-
// plus curated
|
|
8812
|
-
//
|
|
8813
|
-
//
|
|
8814
|
-
//
|
|
8975
|
+
// plus curated and auto-detected aliases, minus blocked names) so
|
|
8976
|
+
// later reporting never borrows renamed or newly added competitors,
|
|
8977
|
+
// or later alias edits or detections, from live project state. A
|
|
8978
|
+
// competitor with no alias of either kind freezes exactly as it did
|
|
8979
|
+
// before aliases existed, so its definition checksum is unchanged;
|
|
8980
|
+
// an auto-detected name moves the checksum only for runs dispatched
|
|
8981
|
+
// after it was applied.
|
|
8815
8982
|
competitors: projectCompetitors.map((competitor) => {
|
|
8816
8983
|
const label = competitorLabelFromDomain(competitor.domain);
|
|
8817
|
-
const
|
|
8818
|
-
return { domain: competitor.domain, label, aliases: [label, ...
|
|
8984
|
+
const named = competitorIdentityAliases(competitor).filter((alias) => alias.toLowerCase() !== label.toLowerCase());
|
|
8985
|
+
return { domain: competitor.domain, label, aliases: [label, ...named] };
|
|
8819
8986
|
}),
|
|
8820
8987
|
queries: projectQueries.map((query) => ({
|
|
8821
8988
|
queryId: query.id,
|
|
@@ -8876,7 +9043,7 @@ var JobRunner = class {
|
|
|
8876
9043
|
}
|
|
8877
9044
|
}
|
|
8878
9045
|
providerCallEnd = Date.now();
|
|
8879
|
-
this.
|
|
9046
|
+
this.reconcileRunAnswerFields(recording, projectId);
|
|
8880
9047
|
this.throwIfRunCancelled(runId);
|
|
8881
9048
|
if (planExecution) {
|
|
8882
9049
|
for (const [provider, units] of planExecution.unitsByProvider) {
|
|
@@ -8914,7 +9081,7 @@ var JobRunner = class {
|
|
|
8914
9081
|
});
|
|
8915
9082
|
if (!finalized && this.isRunCancelled(runId)) throw new RunCancelledError(runId);
|
|
8916
9083
|
} catch (err) {
|
|
8917
|
-
if (recording) this.
|
|
9084
|
+
if (recording) this.reconcileRunAnswerFields(recording, projectId);
|
|
8918
9085
|
const executionContext = {
|
|
8919
9086
|
providerCount: activeProviders.length,
|
|
8920
9087
|
providers: activeProviders.map((provider) => provider.adapter.name),
|
|
@@ -9556,7 +9723,7 @@ var JobRunner = class {
|
|
|
9556
9723
|
this.markBatchesCancelled([batch.id], "Cancelled because its run is no longer running.");
|
|
9557
9724
|
return { kind: "cancelled" };
|
|
9558
9725
|
} finally {
|
|
9559
|
-
this.
|
|
9726
|
+
this.reconcileRunAnswerFields(ctx, batch.projectId);
|
|
9560
9727
|
}
|
|
9561
9728
|
return this.completeBatchIngest(batch);
|
|
9562
9729
|
}
|
|
@@ -9847,7 +10014,7 @@ var JobRunner = class {
|
|
|
9847
10014
|
fatal = describeError(err);
|
|
9848
10015
|
log2.error("fill.failed", { fillId, runId, error: fatal });
|
|
9849
10016
|
} finally {
|
|
9850
|
-
if (recording) this.
|
|
10017
|
+
if (recording) this.reconcileRunAnswerFields(recording, projectId);
|
|
9851
10018
|
this.flushProviderUsage(providerDispatchCounts, providerReservations);
|
|
9852
10019
|
}
|
|
9853
10020
|
const outcome = this.finalizeRunFill({ fillId, runId, filled, fatal, providerErrors, superseded });
|
|
@@ -10097,34 +10264,38 @@ var JobRunner = class {
|
|
|
10097
10264
|
if (!run) throw new Error(`Run ${runId} not found`);
|
|
10098
10265
|
const project = this.db.select().from(projects).where(eq3(projects.id, projectId)).get();
|
|
10099
10266
|
if (!project) throw new Error(`Project ${projectId} not found`);
|
|
10100
|
-
const projectCompetitors = this.db.select().from(competitors).where(eq3(competitors.projectId, projectId)).all();
|
|
10267
|
+
const projectCompetitors = this.db.select().from(competitors).where(eq3(competitors.projectId, projectId)).orderBy(competitors.domain).all();
|
|
10101
10268
|
return runRecordingContext(this.db, {
|
|
10102
10269
|
runId,
|
|
10103
10270
|
measurementPlanVersionId: run.measurementPlanVersionId,
|
|
10104
10271
|
project,
|
|
10105
|
-
competitors: projectCompetitors
|
|
10272
|
+
competitors: projectCompetitors,
|
|
10273
|
+
marketNames: readMarketCompetitorNames(this.db, projectId)
|
|
10106
10274
|
});
|
|
10107
10275
|
}
|
|
10108
10276
|
/**
|
|
10109
|
-
* Rescore a run's stored
|
|
10110
|
-
* changed after `recording` read
|
|
10277
|
+
* Rescore a run's stored answer fields when the identity they are scored
|
|
10278
|
+
* against changed after `recording` read it (`answerIdentityChanged`).
|
|
10111
10279
|
*
|
|
10112
|
-
* Saving a competitor alias
|
|
10113
|
-
*
|
|
10114
|
-
*
|
|
10115
|
-
*
|
|
10116
|
-
*
|
|
10117
|
-
*
|
|
10118
|
-
*
|
|
10280
|
+
* Saving a competitor alias, a detection pass that changes a competitor's
|
|
10281
|
+
* auto-detected names, or a project alias edit rescores the stored answers
|
|
10282
|
+
* once (`onCompetitorAliasesChanged`, `onAliasesChanged`). A writer that read
|
|
10283
|
+
* the identity before the edit keeps scoring with it, so an answer it stores
|
|
10284
|
+
* afterwards would disagree with an identical one the edit rescored. Each
|
|
10285
|
+
* writer calls this after its last write: when its identity is no longer
|
|
10286
|
+
* current, the whole run gets the pass the edit ran, the competitor fields
|
|
10287
|
+
* only, or `answer_mentioned` too when the project's own identity moved. A
|
|
10288
|
+
* failure is logged and never fails the writer.
|
|
10119
10289
|
*/
|
|
10120
|
-
|
|
10290
|
+
reconcileRunAnswerFields(recording, projectId) {
|
|
10121
10291
|
try {
|
|
10122
|
-
const current = this.db
|
|
10123
|
-
if (
|
|
10124
|
-
const
|
|
10125
|
-
|
|
10292
|
+
const current = readAnswerIdentity(this.db, projectId);
|
|
10293
|
+
if (!current || !answerIdentityChanged(recording.identity, current.fingerprint)) return;
|
|
10294
|
+
const full = recording.identity.project !== current.fingerprint.project;
|
|
10295
|
+
const result2 = backfillProjectAnswerMentions(this.db, projectId, { competitorFieldsOnly: !full, runId: recording.runId });
|
|
10296
|
+
log2.info("run.answer-fields-rescored", { runId: recording.runId, projectId, full, ...result2 });
|
|
10126
10297
|
} catch (err) {
|
|
10127
|
-
log2.error("run.
|
|
10298
|
+
log2.error("run.answer-fields-rescore-failed", { runId: recording.runId, projectId, error: describeError(err) });
|
|
10128
10299
|
}
|
|
10129
10300
|
}
|
|
10130
10301
|
incrementUsage(scope, metric, count3) {
|
|
@@ -11142,11 +11313,11 @@ function validateGeoSearchLimit(limit) {
|
|
|
11142
11313
|
);
|
|
11143
11314
|
}
|
|
11144
11315
|
}
|
|
11145
|
-
function
|
|
11316
|
+
function isRecord6(value) {
|
|
11146
11317
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11147
11318
|
}
|
|
11148
11319
|
function validateRequestObject(value, label) {
|
|
11149
|
-
if (!
|
|
11320
|
+
if (!isRecord6(value)) {
|
|
11150
11321
|
throw new OpenAiAdsApiError(`${label} must be a JSON object`, 400);
|
|
11151
11322
|
}
|
|
11152
11323
|
}
|
|
@@ -11305,7 +11476,7 @@ function validateLandingPageConfiguration(value, label) {
|
|
|
11305
11476
|
}
|
|
11306
11477
|
}
|
|
11307
11478
|
function parseLandingPageQueryStringTemplate(value) {
|
|
11308
|
-
if (!
|
|
11479
|
+
if (!isRecord6(value)) return null;
|
|
11309
11480
|
const template = value.query_string_template;
|
|
11310
11481
|
return typeof template === "string" && template.trim().length > 0 ? template : null;
|
|
11311
11482
|
}
|
|
@@ -12899,7 +13070,7 @@ async function executeDiscoveryRun(opts) {
|
|
|
12899
13070
|
try {
|
|
12900
13071
|
const projectRow = opts.db.select().from(projects).where(eq14(projects.id, opts.projectId)).get();
|
|
12901
13072
|
if (!projectRow) throw new Error(`Project ${opts.projectId} not found`);
|
|
12902
|
-
const projectCompetitors = opts.db.select({ domain: competitors.domain }).from(competitors).where(eq14(competitors.projectId, opts.projectId)).all().map((r) => r.domain.toLowerCase());
|
|
13073
|
+
const projectCompetitors = opts.db.select({ domain: competitors.domain }).from(competitors).where(eq14(competitors.projectId, opts.projectId)).orderBy(competitors.domain).all().map((r) => r.domain.toLowerCase());
|
|
12903
13074
|
const canonicalDomains = effectiveDomains({
|
|
12904
13075
|
canonicalDomain: projectRow.canonicalDomain,
|
|
12905
13076
|
ownedDomains: projectRow.ownedDomains
|
|
@@ -14242,6 +14413,115 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
14242
14413
|
}
|
|
14243
14414
|
}
|
|
14244
14415
|
|
|
14416
|
+
// src/answer-field-recompute.ts
|
|
14417
|
+
import { setImmediate as yieldToEventLoop2 } from "timers/promises";
|
|
14418
|
+
function createAnswerFieldRecomputeQueue(opts) {
|
|
14419
|
+
const pendingFull = /* @__PURE__ */ new Map();
|
|
14420
|
+
const queue = createProjectPassQueue(async (projectId) => {
|
|
14421
|
+
const full = pendingFull.get(projectId) ?? false;
|
|
14422
|
+
pendingFull.delete(projectId);
|
|
14423
|
+
await yieldToEventLoop2();
|
|
14424
|
+
const result2 = await backfillProjectAnswerMentionsInChunks(opts.db, projectId, { competitorFieldsOnly: !full });
|
|
14425
|
+
return { full, result: result2 };
|
|
14426
|
+
});
|
|
14427
|
+
return {
|
|
14428
|
+
request(projectId, scope) {
|
|
14429
|
+
pendingFull.set(projectId, (pendingFull.get(projectId) ?? false) || scope.full === true);
|
|
14430
|
+
return queue.request(projectId);
|
|
14431
|
+
},
|
|
14432
|
+
settled: () => queue.settled()
|
|
14433
|
+
};
|
|
14434
|
+
}
|
|
14435
|
+
|
|
14436
|
+
// src/competitor-auto-alias-runner.ts
|
|
14437
|
+
import { setImmediate as yieldToEventLoop3 } from "timers/promises";
|
|
14438
|
+
var log15 = createLogger("CompetitorAutoAliases");
|
|
14439
|
+
function createCompetitorAutoAliasRunner(opts) {
|
|
14440
|
+
const reasons = /* @__PURE__ */ new Map();
|
|
14441
|
+
const queue = createProjectPassQueue(async (projectId) => {
|
|
14442
|
+
await yieldToEventLoop3();
|
|
14443
|
+
const reason = (reasons.get(projectId) ?? []).join(",");
|
|
14444
|
+
reasons.delete(projectId);
|
|
14445
|
+
const result2 = await applyCompetitorAutoAliases(opts.db, projectId, { readAnchors: opts.readAnchors });
|
|
14446
|
+
if (!result2) return null;
|
|
14447
|
+
const added = result2.detection.competitors.reduce((sum, competitor) => sum + competitor.added.length, 0);
|
|
14448
|
+
const removed = result2.detection.competitors.reduce((sum, competitor) => sum + competitor.removed.length, 0);
|
|
14449
|
+
log15.info("detection.completed", {
|
|
14450
|
+
projectId,
|
|
14451
|
+
reason,
|
|
14452
|
+
runs: result2.detection.scan.runs,
|
|
14453
|
+
snapshots: result2.detection.scan.snapshots,
|
|
14454
|
+
added,
|
|
14455
|
+
removed,
|
|
14456
|
+
namesChanged: result2.namesChanged
|
|
14457
|
+
});
|
|
14458
|
+
if (result2.namesChanged) {
|
|
14459
|
+
try {
|
|
14460
|
+
opts.onNamesChanged(projectId, result2.detection.project);
|
|
14461
|
+
} catch (err) {
|
|
14462
|
+
log15.error("names-changed.failed", { projectId, error: describeError(err) });
|
|
14463
|
+
}
|
|
14464
|
+
}
|
|
14465
|
+
return result2;
|
|
14466
|
+
});
|
|
14467
|
+
const enqueue = (projectId, reason) => {
|
|
14468
|
+
reasons.set(projectId, [...reasons.get(projectId) ?? [], reason]);
|
|
14469
|
+
return queue.request(projectId);
|
|
14470
|
+
};
|
|
14471
|
+
return {
|
|
14472
|
+
schedule(projectId, reason) {
|
|
14473
|
+
enqueue(projectId, reason).catch((err) => {
|
|
14474
|
+
log15.error("detection.failed", { projectId, reason, error: describeError(err) });
|
|
14475
|
+
});
|
|
14476
|
+
},
|
|
14477
|
+
async request(projectId, reason) {
|
|
14478
|
+
try {
|
|
14479
|
+
return await enqueue(projectId, reason);
|
|
14480
|
+
} catch (err) {
|
|
14481
|
+
log15.error("detection.failed", { projectId, reason, error: describeError(err) });
|
|
14482
|
+
throw err;
|
|
14483
|
+
}
|
|
14484
|
+
},
|
|
14485
|
+
settled: () => queue.settled()
|
|
14486
|
+
};
|
|
14487
|
+
}
|
|
14488
|
+
|
|
14489
|
+
// src/stored-answer-anchors.ts
|
|
14490
|
+
function resolveStoredApiResponse(parsed) {
|
|
14491
|
+
const nested = parsed.apiResponse;
|
|
14492
|
+
if (nested !== null && typeof nested === "object" && !Array.isArray(nested)) {
|
|
14493
|
+
return nested;
|
|
14494
|
+
}
|
|
14495
|
+
if (looksLikeProviderApiResponse(parsed)) {
|
|
14496
|
+
return parsed;
|
|
14497
|
+
}
|
|
14498
|
+
return null;
|
|
14499
|
+
}
|
|
14500
|
+
function looksLikeProviderApiResponse(value) {
|
|
14501
|
+
return Array.isArray(value.output) || Array.isArray(value.content) || Array.isArray(value.candidates) || Array.isArray(value.choices);
|
|
14502
|
+
}
|
|
14503
|
+
function extractStoredAnswerAnchors(provider, rawResponse) {
|
|
14504
|
+
if (!rawResponse) return [];
|
|
14505
|
+
const apiResponse = resolveStoredApiResponse(parseJsonColumn(rawResponse, {}));
|
|
14506
|
+
if (!apiResponse) return [];
|
|
14507
|
+
try {
|
|
14508
|
+
switch (provider) {
|
|
14509
|
+
case ProviderNames.openai:
|
|
14510
|
+
return extractAnchoredSpans2(apiResponse);
|
|
14511
|
+
case ProviderNames.claude:
|
|
14512
|
+
return extractAnchoredSpans3(apiResponse);
|
|
14513
|
+
case ProviderNames.gemini:
|
|
14514
|
+
return extractAnchoredSpans(apiResponse);
|
|
14515
|
+
case ProviderNames.perplexity:
|
|
14516
|
+
return extractAnchoredSpans4(apiResponse);
|
|
14517
|
+
default:
|
|
14518
|
+
return [];
|
|
14519
|
+
}
|
|
14520
|
+
} catch {
|
|
14521
|
+
return [];
|
|
14522
|
+
}
|
|
14523
|
+
}
|
|
14524
|
+
|
|
14245
14525
|
// src/provider-registry.ts
|
|
14246
14526
|
var ProviderRegistry = class {
|
|
14247
14527
|
providers = /* @__PURE__ */ new Map();
|
|
@@ -14358,7 +14638,7 @@ function createProviderModelCatalog(registry) {
|
|
|
14358
14638
|
import crypto21 from "crypto";
|
|
14359
14639
|
import cron from "node-cron";
|
|
14360
14640
|
import { and as and15, eq as eq17, inArray as inArray7, notExists, sql as sql7 } from "drizzle-orm";
|
|
14361
|
-
var
|
|
14641
|
+
var log16 = createLogger("Scheduler");
|
|
14362
14642
|
var DEFAULT_HEALTH_CRON = "0 */6 * * *";
|
|
14363
14643
|
function ensureDefaultHealthSchedule(db, projectId, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
14364
14644
|
const result2 = db.insert(schedules).values({
|
|
@@ -14420,7 +14700,7 @@ var Scheduler = class {
|
|
|
14420
14700
|
if (ensureDefaultHealthSchedule(this.db, project.id, now)) seeded += 1;
|
|
14421
14701
|
}
|
|
14422
14702
|
if (seeded > 0) {
|
|
14423
|
-
|
|
14703
|
+
log16.info("health-schedule.seeded", { projectCount: seeded, cron: DEFAULT_HEALTH_CRON });
|
|
14424
14704
|
}
|
|
14425
14705
|
}
|
|
14426
14706
|
/**
|
|
@@ -14444,10 +14724,10 @@ var Scheduler = class {
|
|
|
14444
14724
|
const current = ensureCurrentQueryBasketRevision(this.db, project.id);
|
|
14445
14725
|
if (current !== null && current.revision !== previous) minted += 1;
|
|
14446
14726
|
} catch (err) {
|
|
14447
|
-
|
|
14727
|
+
log16.warn("query-basket.mint-failed", { projectId: project.id, err: String(err) });
|
|
14448
14728
|
}
|
|
14449
14729
|
}
|
|
14450
|
-
if (minted > 0)
|
|
14730
|
+
if (minted > 0) log16.info("query-basket.ensured", { projectCount: minted });
|
|
14451
14731
|
}
|
|
14452
14732
|
/** Load all enabled schedules from DB and register cron jobs. */
|
|
14453
14733
|
start() {
|
|
@@ -14461,12 +14741,12 @@ var Scheduler = class {
|
|
|
14461
14741
|
if (registered && missedRunAt && new Date(missedRunAt) < /* @__PURE__ */ new Date()) {
|
|
14462
14742
|
const answerRecurrence = recurrence && schedule.kind === SchedulableRunKinds["answer-visibility"];
|
|
14463
14743
|
if (!recurrence || answerRecurrence || this.claimCalendarOccurrence(schedule, missedRunAt, /* @__PURE__ */ new Date())) {
|
|
14464
|
-
|
|
14744
|
+
log16.info("run.catch-up", { projectId: schedule.projectId, kind: schedule.kind, missedRunAt });
|
|
14465
14745
|
this.triggerRun(schedule.id, schedule.projectId, schedule.kind, answerRecurrence ? missedRunAt : void 0);
|
|
14466
14746
|
}
|
|
14467
14747
|
}
|
|
14468
14748
|
}
|
|
14469
|
-
|
|
14749
|
+
log16.info("started", { scheduleCount: allSchedules.length });
|
|
14470
14750
|
}
|
|
14471
14751
|
/** Stop all cron tasks for graceful shutdown. */
|
|
14472
14752
|
stop() {
|
|
@@ -14510,7 +14790,7 @@ var Scheduler = class {
|
|
|
14510
14790
|
stopTask(key, task, verb) {
|
|
14511
14791
|
void task.stop();
|
|
14512
14792
|
void task.destroy();
|
|
14513
|
-
|
|
14793
|
+
log16.info(`task.${verb.toLowerCase()}`, { key });
|
|
14514
14794
|
}
|
|
14515
14795
|
/** Runtime timing writes must not restore a version already issued to a settings caller. */
|
|
14516
14796
|
updateScheduleTiming(scheduleId, timing) {
|
|
@@ -14530,13 +14810,13 @@ var Scheduler = class {
|
|
|
14530
14810
|
return true;
|
|
14531
14811
|
}
|
|
14532
14812
|
if (schedule.recurrence) {
|
|
14533
|
-
|
|
14813
|
+
log16.error("calendar.invalid", { projectId: schedule.projectId, kind: schedule.kind });
|
|
14534
14814
|
return false;
|
|
14535
14815
|
}
|
|
14536
14816
|
const { id: scheduleId, projectId, cronExpr, timezone } = schedule;
|
|
14537
14817
|
const kind = schedule.kind;
|
|
14538
14818
|
if (!cron.validate(cronExpr)) {
|
|
14539
|
-
|
|
14819
|
+
log16.error("cron.invalid", { projectId, kind, cronExpr });
|
|
14540
14820
|
return false;
|
|
14541
14821
|
}
|
|
14542
14822
|
const task = cron.schedule(cronExpr, () => {
|
|
@@ -14551,7 +14831,7 @@ var Scheduler = class {
|
|
|
14551
14831
|
});
|
|
14552
14832
|
}
|
|
14553
14833
|
const label = schedule.preset ?? cronExpr;
|
|
14554
|
-
|
|
14834
|
+
log16.info("cron.registered", { projectId, kind, schedule: label, timezone });
|
|
14555
14835
|
return true;
|
|
14556
14836
|
}
|
|
14557
14837
|
/** Calendar schedules are checked at most once a minute so long intervals
|
|
@@ -14609,7 +14889,7 @@ var Scheduler = class {
|
|
|
14609
14889
|
}
|
|
14610
14890
|
if (options.preserveNextRunAt) arm(registeredNextRunAt);
|
|
14611
14891
|
else timer = setTimeout(tick, 0);
|
|
14612
|
-
|
|
14892
|
+
log16.info("calendar.registered", { projectId, kind, recurrence, timezone });
|
|
14613
14893
|
}
|
|
14614
14894
|
/** Atomically advance a due callback-only calendar occurrence before dispatch. */
|
|
14615
14895
|
claimCalendarOccurrence(schedule, dueAt, now) {
|
|
@@ -14637,13 +14917,13 @@ var Scheduler = class {
|
|
|
14637
14917
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14638
14918
|
const currentSchedule = this.db.select().from(schedules).where(eq17(schedules.id, scheduleId)).get();
|
|
14639
14919
|
if (!currentSchedule || !currentSchedule.enabled) {
|
|
14640
|
-
|
|
14920
|
+
log16.warn("schedule.stale", { scheduleId, projectId, kind, msg: "schedule no longer exists or is disabled" });
|
|
14641
14921
|
this.remove(projectId, kind);
|
|
14642
14922
|
return;
|
|
14643
14923
|
}
|
|
14644
14924
|
const recurrence = scheduleRecurrence(currentSchedule);
|
|
14645
14925
|
if (claimedOccurrence && (!recurrence || currentSchedule.nextRunAt !== claimedOccurrence)) {
|
|
14646
|
-
|
|
14926
|
+
log16.info("calendar.stale-claim", { projectId, scheduleId, kind });
|
|
14647
14927
|
return;
|
|
14648
14928
|
}
|
|
14649
14929
|
const nextRunAt = nextRunFromSchedule({
|
|
@@ -14653,31 +14933,31 @@ var Scheduler = class {
|
|
|
14653
14933
|
});
|
|
14654
14934
|
const project = this.db.select().from(projects).where(eq17(projects.id, projectId)).get();
|
|
14655
14935
|
if (!project) {
|
|
14656
|
-
|
|
14936
|
+
log16.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
|
|
14657
14937
|
this.remove(projectId, kind);
|
|
14658
14938
|
return;
|
|
14659
14939
|
}
|
|
14660
14940
|
if (kind === SchedulableRunKinds["traffic-sync"]) {
|
|
14661
14941
|
const sourceId = currentSchedule.sourceId;
|
|
14662
14942
|
if (!sourceId) {
|
|
14663
|
-
|
|
14943
|
+
log16.warn("traffic-sync.missing-source", { scheduleId, projectId });
|
|
14664
14944
|
return;
|
|
14665
14945
|
}
|
|
14666
14946
|
if (!this.callbacks.onTrafficSyncRequested) {
|
|
14667
|
-
|
|
14947
|
+
log16.warn("traffic-sync.no-callback", { scheduleId, projectId, msg: "host did not register onTrafficSyncRequested" });
|
|
14668
14948
|
return;
|
|
14669
14949
|
}
|
|
14670
14950
|
this.updateScheduleTiming(currentSchedule.id, {
|
|
14671
14951
|
lastRunAt: now,
|
|
14672
14952
|
nextRunAt
|
|
14673
14953
|
});
|
|
14674
|
-
|
|
14954
|
+
log16.info("traffic-sync.triggered", { projectName: project.name, sourceId });
|
|
14675
14955
|
this.callbacks.onTrafficSyncRequested(project.name, sourceId);
|
|
14676
14956
|
return;
|
|
14677
14957
|
}
|
|
14678
14958
|
if (kind === SchedulableRunKinds["gbp-sync"]) {
|
|
14679
14959
|
if (!this.callbacks.onGbpSyncRequested) {
|
|
14680
|
-
|
|
14960
|
+
log16.warn("gbp-sync.no-callback", { scheduleId, projectId, msg: "host did not register onGbpSyncRequested" });
|
|
14681
14961
|
return;
|
|
14682
14962
|
}
|
|
14683
14963
|
const runId2 = crypto21.randomUUID();
|
|
@@ -14693,13 +14973,13 @@ var Scheduler = class {
|
|
|
14693
14973
|
lastRunAt: now,
|
|
14694
14974
|
nextRunAt
|
|
14695
14975
|
});
|
|
14696
|
-
|
|
14976
|
+
log16.info("gbp-sync.triggered", { runId: runId2, projectName: project.name });
|
|
14697
14977
|
this.callbacks.onGbpSyncRequested(runId2, projectId);
|
|
14698
14978
|
return;
|
|
14699
14979
|
}
|
|
14700
14980
|
if (kind === SchedulableRunKinds["ads-sync"]) {
|
|
14701
14981
|
if (!this.callbacks.onAdsSyncRequested) {
|
|
14702
|
-
|
|
14982
|
+
log16.warn("ads-sync.no-callback", { scheduleId, projectId, msg: "host did not register onAdsSyncRequested" });
|
|
14703
14983
|
return;
|
|
14704
14984
|
}
|
|
14705
14985
|
const activeAdsRun = this.db.select({ id: runs.id }).from(runs).where(and15(
|
|
@@ -14708,7 +14988,7 @@ var Scheduler = class {
|
|
|
14708
14988
|
inArray7(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
14709
14989
|
)).get();
|
|
14710
14990
|
if (activeAdsRun) {
|
|
14711
|
-
|
|
14991
|
+
log16.info("ads-sync.skipped-active", { projectName: project.name, activeRunId: activeAdsRun.id });
|
|
14712
14992
|
this.updateScheduleTiming(currentSchedule.id, { nextRunAt });
|
|
14713
14993
|
return;
|
|
14714
14994
|
}
|
|
@@ -14725,52 +15005,52 @@ var Scheduler = class {
|
|
|
14725
15005
|
lastRunAt: now,
|
|
14726
15006
|
nextRunAt
|
|
14727
15007
|
});
|
|
14728
|
-
|
|
15008
|
+
log16.info("ads-sync.triggered", { runId: runId2, projectName: project.name });
|
|
14729
15009
|
this.callbacks.onAdsSyncRequested(runId2, projectId);
|
|
14730
15010
|
return;
|
|
14731
15011
|
}
|
|
14732
15012
|
if (kind === SchedulableRunKinds["data-refresh"]) {
|
|
14733
15013
|
if (!this.callbacks.onDataRefreshRequested) {
|
|
14734
|
-
|
|
15014
|
+
log16.warn("data-refresh.no-callback", { scheduleId, projectId, msg: "host did not register onDataRefreshRequested" });
|
|
14735
15015
|
return;
|
|
14736
15016
|
}
|
|
14737
15017
|
this.updateScheduleTiming(currentSchedule.id, {
|
|
14738
15018
|
lastRunAt: now,
|
|
14739
15019
|
nextRunAt
|
|
14740
15020
|
});
|
|
14741
|
-
|
|
15021
|
+
log16.info("data-refresh.triggered", { projectName: project.name });
|
|
14742
15022
|
this.callbacks.onDataRefreshRequested(project.name);
|
|
14743
15023
|
return;
|
|
14744
15024
|
}
|
|
14745
15025
|
if (kind === SchedulableRunKinds.doctor) {
|
|
14746
15026
|
if (!this.callbacks.onDoctorRequested) {
|
|
14747
|
-
|
|
15027
|
+
log16.warn("doctor.no-callback", { scheduleId, projectId, msg: "host did not register onDoctorRequested" });
|
|
14748
15028
|
return;
|
|
14749
15029
|
}
|
|
14750
15030
|
this.updateScheduleTiming(currentSchedule.id, {
|
|
14751
15031
|
lastRunAt: now,
|
|
14752
15032
|
nextRunAt
|
|
14753
15033
|
});
|
|
14754
|
-
|
|
15034
|
+
log16.info("doctor.triggered", { projectName: project.name });
|
|
14755
15035
|
this.callbacks.onDoctorRequested(project.name);
|
|
14756
15036
|
return;
|
|
14757
15037
|
}
|
|
14758
15038
|
if (kind === SchedulableRunKinds["backlinks-sync"]) {
|
|
14759
15039
|
if (!this.callbacks.onBacklinksSyncRequested) {
|
|
14760
|
-
|
|
15040
|
+
log16.warn("backlinks-sync.no-callback", { scheduleId, projectId, msg: "host did not register onBacklinksSyncRequested" });
|
|
14761
15041
|
return;
|
|
14762
15042
|
}
|
|
14763
15043
|
this.updateScheduleTiming(currentSchedule.id, {
|
|
14764
15044
|
lastRunAt: now,
|
|
14765
15045
|
nextRunAt
|
|
14766
15046
|
});
|
|
14767
|
-
|
|
15047
|
+
log16.info("backlinks-sync.triggered", { projectName: project.name });
|
|
14768
15048
|
this.callbacks.onBacklinksSyncRequested(project.name);
|
|
14769
15049
|
return;
|
|
14770
15050
|
}
|
|
14771
15051
|
if (kind === SchedulableRunKinds["site-audit"]) {
|
|
14772
15052
|
if (!this.callbacks.onSiteAuditRequested) {
|
|
14773
|
-
|
|
15053
|
+
log16.warn("site-audit.no-callback", { scheduleId, projectId, msg: "host did not register onSiteAuditRequested" });
|
|
14774
15054
|
return;
|
|
14775
15055
|
}
|
|
14776
15056
|
const active = this.db.select({ id: runs.id }).from(runs).where(and15(
|
|
@@ -14779,7 +15059,7 @@ var Scheduler = class {
|
|
|
14779
15059
|
inArray7(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
14780
15060
|
)).get();
|
|
14781
15061
|
if (active) {
|
|
14782
|
-
|
|
15062
|
+
log16.info("site-audit.skipped-active", { projectName: project.name, activeRunId: active.id });
|
|
14783
15063
|
this.updateScheduleTiming(currentSchedule.id, { nextRunAt });
|
|
14784
15064
|
return;
|
|
14785
15065
|
}
|
|
@@ -14806,7 +15086,7 @@ var Scheduler = class {
|
|
|
14806
15086
|
lastRunAt: now,
|
|
14807
15087
|
nextRunAt
|
|
14808
15088
|
});
|
|
14809
|
-
|
|
15089
|
+
log16.info("site-audit.triggered", { runId: runId2, projectName: project.name });
|
|
14810
15090
|
this.callbacks.onSiteAuditRequested(runId2, projectId, { maxPages: effectiveRequest.maxPages });
|
|
14811
15091
|
return;
|
|
14812
15092
|
}
|
|
@@ -14815,7 +15095,7 @@ var Scheduler = class {
|
|
|
14815
15095
|
if (project.defaultLocation) {
|
|
14816
15096
|
const loc = projectLocations.find((l) => l.label === project.defaultLocation);
|
|
14817
15097
|
if (!loc) {
|
|
14818
|
-
|
|
15098
|
+
log16.warn("default-location.stale", { scheduleId, projectId, label: project.defaultLocation });
|
|
14819
15099
|
return;
|
|
14820
15100
|
}
|
|
14821
15101
|
resolvedLocation = loc;
|
|
@@ -14824,7 +15104,7 @@ var Scheduler = class {
|
|
|
14824
15104
|
const scheduleProviders = currentSchedule.providers;
|
|
14825
15105
|
const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
|
|
14826
15106
|
if (claimedOccurrence && recurrence && !nextRunAt) {
|
|
14827
|
-
|
|
15107
|
+
log16.error("calendar.invalid", { projectId, kind, scheduleId });
|
|
14828
15108
|
return;
|
|
14829
15109
|
}
|
|
14830
15110
|
const queueResult = queueRunIfProjectIdle(this.db, {
|
|
@@ -14848,11 +15128,11 @@ var Scheduler = class {
|
|
|
14848
15128
|
});
|
|
14849
15129
|
if (queueResult.conflict) {
|
|
14850
15130
|
if (queueResult.scheduleClaimed === false) {
|
|
14851
|
-
|
|
15131
|
+
log16.info("calendar.skipped-claimed", { projectName: project.name, scheduleId: currentSchedule.id });
|
|
14852
15132
|
return;
|
|
14853
15133
|
}
|
|
14854
15134
|
const batchPending = hasOutstandingProviderBatch(this.db, queueResult.activeRunId);
|
|
14855
|
-
|
|
15135
|
+
log16.info("run.skipped-active", {
|
|
14856
15136
|
projectName: project.name,
|
|
14857
15137
|
activeRunId: queueResult.activeRunId,
|
|
14858
15138
|
...batchPending ? { reason: "batch-pending" } : {}
|
|
@@ -14861,7 +15141,7 @@ var Scheduler = class {
|
|
|
14861
15141
|
return;
|
|
14862
15142
|
}
|
|
14863
15143
|
if (queueResult.refused) {
|
|
14864
|
-
|
|
15144
|
+
log16.warn("run.skipped-providers-failing", {
|
|
14865
15145
|
projectName: project.name,
|
|
14866
15146
|
scheduleId: currentSchedule.id,
|
|
14867
15147
|
...queueResult.refused
|
|
@@ -14872,7 +15152,7 @@ var Scheduler = class {
|
|
|
14872
15152
|
const runId = queueResult.runId;
|
|
14873
15153
|
for (const [provider, reason] of Object.entries(queueResult.dispatch.ineligible)) {
|
|
14874
15154
|
if (!reason) continue;
|
|
14875
|
-
|
|
15155
|
+
log16.warn("run.dispatch-sync-fallback", {
|
|
14876
15156
|
runId,
|
|
14877
15157
|
projectName: project.name,
|
|
14878
15158
|
provider,
|
|
@@ -14886,16 +15166,16 @@ var Scheduler = class {
|
|
|
14886
15166
|
lastRunAt: now,
|
|
14887
15167
|
nextRunAt
|
|
14888
15168
|
});
|
|
14889
|
-
|
|
15169
|
+
log16.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
|
|
14890
15170
|
this.callbacks.onRunCreated(runId, projectId, providers, resolvedLocation);
|
|
14891
15171
|
} catch (err) {
|
|
14892
|
-
|
|
15172
|
+
log16.error("trigger.error", { scheduleId, projectId, kind, error: describeError(err) });
|
|
14893
15173
|
}
|
|
14894
15174
|
}
|
|
14895
15175
|
};
|
|
14896
15176
|
|
|
14897
15177
|
// src/site-liveness-loop.ts
|
|
14898
|
-
var
|
|
15178
|
+
var log17 = createLogger("SiteLiveness");
|
|
14899
15179
|
var SITE_LIVENESS_INTERVAL_MS = 10 * 6e4;
|
|
14900
15180
|
async function runSiteLivenessPass(deps) {
|
|
14901
15181
|
const now = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
@@ -14910,10 +15190,10 @@ async function runSiteLivenessPass(deps) {
|
|
|
14910
15190
|
const event = await deps.notify(project.id, { check, checkedAt: now() });
|
|
14911
15191
|
if (event) {
|
|
14912
15192
|
events += 1;
|
|
14913
|
-
|
|
15193
|
+
log17.info("site-liveness.notified", { projectName: project.name, event, code: check.code });
|
|
14914
15194
|
}
|
|
14915
15195
|
} catch (err) {
|
|
14916
|
-
|
|
15196
|
+
log17.warn("site-liveness.project-failed", { projectName: project.name, err: String(err) });
|
|
14917
15197
|
}
|
|
14918
15198
|
}
|
|
14919
15199
|
return { checked, events };
|
|
@@ -14922,11 +15202,11 @@ function startSiteLivenessLoop(deps, intervalMs = SITE_LIVENESS_INTERVAL_MS) {
|
|
|
14922
15202
|
let running = false;
|
|
14923
15203
|
const timer = setInterval(() => {
|
|
14924
15204
|
if (running) {
|
|
14925
|
-
|
|
15205
|
+
log17.warn("site-liveness.pass-still-running", { intervalMs });
|
|
14926
15206
|
return;
|
|
14927
15207
|
}
|
|
14928
15208
|
running = true;
|
|
14929
|
-
void runSiteLivenessPass(deps).catch((err) =>
|
|
15209
|
+
void runSiteLivenessPass(deps).catch((err) => log17.warn("site-liveness.pass-failed", { err: String(err) })).finally(() => {
|
|
14930
15210
|
running = false;
|
|
14931
15211
|
});
|
|
14932
15212
|
}, intervalMs);
|
|
@@ -14936,7 +15216,7 @@ function startSiteLivenessLoop(deps, intervalMs = SITE_LIVENESS_INTERVAL_MS) {
|
|
|
14936
15216
|
|
|
14937
15217
|
// src/provider-batch-poller.ts
|
|
14938
15218
|
import { and as and16, eq as eq18, inArray as inArray8, isNotNull as isNotNull2, isNull as isNull2, notExists as notExists2, sql as sql8 } from "drizzle-orm";
|
|
14939
|
-
var
|
|
15219
|
+
var log18 = createLogger("ProviderBatchPoller");
|
|
14940
15220
|
var PROVIDER_BATCH_POLL_INITIAL_MS = 3e4;
|
|
14941
15221
|
var PROVIDER_BATCH_POLL_MAX_MS = 10 * 6e4;
|
|
14942
15222
|
var PROVIDER_BATCH_CANCEL_GRACE_MS = 60 * 6e4;
|
|
@@ -14960,7 +15240,7 @@ var ProviderBatchPoller = class {
|
|
|
14960
15240
|
try {
|
|
14961
15241
|
if (await this.advance(row)) settledRuns.set(row.runId, row.projectId);
|
|
14962
15242
|
} catch (err) {
|
|
14963
|
-
|
|
15243
|
+
log18.warn("batch.advance-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error: describeError(err) });
|
|
14964
15244
|
this.reschedule(row.id, null);
|
|
14965
15245
|
}
|
|
14966
15246
|
}
|
|
@@ -14969,7 +15249,7 @@ var ProviderBatchPoller = class {
|
|
|
14969
15249
|
try {
|
|
14970
15250
|
this.deps.runner.finalizeBatchRun(runId, projectId);
|
|
14971
15251
|
} catch (err) {
|
|
14972
|
-
|
|
15252
|
+
log18.warn("batch.finalize-failed", { runId, error: describeError(err) });
|
|
14973
15253
|
}
|
|
14974
15254
|
}
|
|
14975
15255
|
}
|
|
@@ -15000,10 +15280,10 @@ var ProviderBatchPoller = class {
|
|
|
15000
15280
|
return ended ? this.ingest({ ...row, status: ProviderBatchStatuses.ended, resultsExpireAt: poll.resultsExpireAt ?? null }) : false;
|
|
15001
15281
|
}
|
|
15002
15282
|
} catch (err) {
|
|
15003
|
-
|
|
15283
|
+
log18.warn("batch.poll-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error: describeError(err) });
|
|
15004
15284
|
}
|
|
15005
15285
|
} else {
|
|
15006
|
-
|
|
15286
|
+
log18.warn("batch.poll-unavailable", { batchId: row.id, runId: row.runId, provider: row.provider });
|
|
15007
15287
|
}
|
|
15008
15288
|
this.reschedule(row.id, status);
|
|
15009
15289
|
return this.enforceDeadline(row, registered);
|
|
@@ -15025,12 +15305,12 @@ var ProviderBatchPoller = class {
|
|
|
15025
15305
|
isNull2(providerBatches.cancelRequestedAt)
|
|
15026
15306
|
)).run().changes === 1;
|
|
15027
15307
|
if (!claimed) return false;
|
|
15028
|
-
|
|
15308
|
+
log18.warn("batch.deadline-passed", { batchId: row.id, runId: row.runId, provider: row.provider, deadlineAt: row.deadlineAt });
|
|
15029
15309
|
if (registered?.adapter.batch && row.providerBatchId) {
|
|
15030
15310
|
try {
|
|
15031
15311
|
await registered.adapter.batch.cancel(row.providerBatchId, registered.config);
|
|
15032
15312
|
} catch (err) {
|
|
15033
|
-
|
|
15313
|
+
log18.warn("batch.cancel-failed", { batchId: row.id, provider: row.provider, error: describeError(err) });
|
|
15034
15314
|
}
|
|
15035
15315
|
}
|
|
15036
15316
|
return false;
|
|
@@ -15042,7 +15322,7 @@ var ProviderBatchPoller = class {
|
|
|
15042
15322
|
updatedAt: at
|
|
15043
15323
|
}).where(and16(eq18(providerBatches.id, row.id), eq18(providerBatches.status, ProviderBatchStatuses.submitted))).run().changes === 1;
|
|
15044
15324
|
this.schedule.delete(row.id);
|
|
15045
|
-
if (gaveUp)
|
|
15325
|
+
if (gaveUp) log18.warn("batch.abandoned-after-deadline", { batchId: row.id, runId: row.runId, provider: row.provider });
|
|
15046
15326
|
return gaveUp;
|
|
15047
15327
|
}
|
|
15048
15328
|
async ingest(row) {
|
|
@@ -15051,7 +15331,7 @@ var ProviderBatchPoller = class {
|
|
|
15051
15331
|
return result2.kind !== "skipped";
|
|
15052
15332
|
} catch (err) {
|
|
15053
15333
|
const error = describeError(err);
|
|
15054
|
-
|
|
15334
|
+
log18.warn("batch.ingest-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error });
|
|
15055
15335
|
const now = this.now();
|
|
15056
15336
|
const unreadable = row.resultsExpireAt !== null && now >= Date.parse(row.resultsExpireAt);
|
|
15057
15337
|
if (unreadable || now >= Date.parse(row.deadlineAt) + PROVIDER_BATCH_CANCEL_GRACE_MS) {
|
|
@@ -15093,7 +15373,7 @@ function startProviderBatchPoller(deps, intervalMs = PROVIDER_BATCH_TICK_MS) {
|
|
|
15093
15373
|
const timer = setInterval(() => {
|
|
15094
15374
|
if (running) return;
|
|
15095
15375
|
running = true;
|
|
15096
|
-
void poller.tick().catch((err) =>
|
|
15376
|
+
void poller.tick().catch((err) => log18.warn("batch.pass-failed", { error: describeError(err) })).finally(() => {
|
|
15097
15377
|
running = false;
|
|
15098
15378
|
});
|
|
15099
15379
|
}, intervalMs);
|
|
@@ -15102,7 +15382,7 @@ function startProviderBatchPoller(deps, intervalMs = PROVIDER_BATCH_TICK_MS) {
|
|
|
15102
15382
|
}
|
|
15103
15383
|
|
|
15104
15384
|
// src/data-refresh.ts
|
|
15105
|
-
var
|
|
15385
|
+
var log19 = createLogger("DataRefresh");
|
|
15106
15386
|
function gaRefreshDays(now = /* @__PURE__ */ new Date()) {
|
|
15107
15387
|
const [reportMonth] = reportMonthsForDoctor(void 0, now);
|
|
15108
15388
|
const reach = inclusiveDayCount(`${reportMonth}-01`, now.toISOString().slice(0, 10)) ?? 0;
|
|
@@ -15124,11 +15404,11 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
|
|
|
15124
15404
|
results.forEach((result2, idx) => {
|
|
15125
15405
|
const integration = integrations[idx].name;
|
|
15126
15406
|
if (result2.status === "fulfilled") {
|
|
15127
|
-
|
|
15407
|
+
log19.info("integration.refreshed", { projectName, integration });
|
|
15128
15408
|
} else {
|
|
15129
15409
|
const reason = result2.reason;
|
|
15130
15410
|
const message = describeError(reason);
|
|
15131
|
-
|
|
15411
|
+
log19.warn("integration.refresh-failed", { projectName, integration, error: message });
|
|
15132
15412
|
}
|
|
15133
15413
|
});
|
|
15134
15414
|
}
|
|
@@ -15136,7 +15416,7 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
|
|
|
15136
15416
|
// src/notifier.ts
|
|
15137
15417
|
import { eq as eq19, desc as desc8, and as and17, inArray as inArray9, or } from "drizzle-orm";
|
|
15138
15418
|
import crypto22 from "crypto";
|
|
15139
|
-
var
|
|
15419
|
+
var log20 = createLogger("Notifier");
|
|
15140
15420
|
var MATERIAL_MAGNITUDE_DELTA = 20;
|
|
15141
15421
|
var SEVERITY_RANK = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
15142
15422
|
function severityRank(severity) {
|
|
@@ -15166,26 +15446,26 @@ var Notifier = class {
|
|
|
15166
15446
|
}
|
|
15167
15447
|
/** Called after a run completes (success, partial, or failed). */
|
|
15168
15448
|
async onRunCompleted(runId, projectId, opts) {
|
|
15169
|
-
|
|
15449
|
+
log20.info("run.completed", { runId, projectId, origin: opts?.origin ?? "sweep" });
|
|
15170
15450
|
const notifs = this.db.select().from(notifications).where(eq19(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
15171
15451
|
if (notifs.length === 0) {
|
|
15172
|
-
|
|
15452
|
+
log20.info("notifications.none-enabled", { projectId });
|
|
15173
15453
|
return;
|
|
15174
15454
|
}
|
|
15175
|
-
|
|
15455
|
+
log20.info("notifications.found", { projectId, count: notifs.length });
|
|
15176
15456
|
const run = this.db.select().from(runs).where(eq19(runs.id, runId)).get();
|
|
15177
15457
|
if (!run) {
|
|
15178
|
-
|
|
15458
|
+
log20.error("run.not-found", { runId, msg: "skipping notification dispatch" });
|
|
15179
15459
|
return;
|
|
15180
15460
|
}
|
|
15181
15461
|
const project = this.db.select().from(projects).where(eq19(projects.id, projectId)).get();
|
|
15182
15462
|
if (!project) {
|
|
15183
|
-
|
|
15463
|
+
log20.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
|
|
15184
15464
|
return;
|
|
15185
15465
|
}
|
|
15186
15466
|
const transitions = this.computeTransitions(runId, projectId);
|
|
15187
15467
|
const events = [];
|
|
15188
|
-
|
|
15468
|
+
log20.info("run.status", { runId: run.id, status: run.status, projectId });
|
|
15189
15469
|
if ((run.status === "completed" || run.status === "partial") && opts?.origin !== "fill") {
|
|
15190
15470
|
events.push("run.completed");
|
|
15191
15471
|
}
|
|
@@ -15201,7 +15481,7 @@ var Notifier = class {
|
|
|
15201
15481
|
if (!config.url) continue;
|
|
15202
15482
|
const subscribedEvents = config.events;
|
|
15203
15483
|
const matchingEvents = events.filter((e) => subscribedEvents.includes(e));
|
|
15204
|
-
|
|
15484
|
+
log20.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
|
|
15205
15485
|
if (matchingEvents.length === 0) continue;
|
|
15206
15486
|
for (const event of matchingEvents) {
|
|
15207
15487
|
const relevantTransitions = event === "citation.lost" ? lostTransitions : event === "citation.gained" ? gainedTransitions : transitions;
|
|
@@ -15233,7 +15513,7 @@ var Notifier = class {
|
|
|
15233
15513
|
async onHealthChecked(projectId, report) {
|
|
15234
15514
|
const project = this.db.select().from(projects).where(eq19(projects.id, projectId)).get();
|
|
15235
15515
|
if (!project) {
|
|
15236
|
-
|
|
15516
|
+
log20.error("project.not-found", { projectId, msg: "skipping health notification" });
|
|
15237
15517
|
return null;
|
|
15238
15518
|
}
|
|
15239
15519
|
const rank = (status2) => status2 === "fail" ? 2 : status2 === "warn" ? 1 : 0;
|
|
@@ -15285,7 +15565,7 @@ var Notifier = class {
|
|
|
15285
15565
|
this.db.update(doctorHealthState).set(observation).where(eq19(doctorHealthState.projectId, projectId)).run();
|
|
15286
15566
|
}
|
|
15287
15567
|
if (!event) {
|
|
15288
|
-
|
|
15568
|
+
log20.info("health.unchanged", { projectId, status, code });
|
|
15289
15569
|
return null;
|
|
15290
15570
|
}
|
|
15291
15571
|
const notifs = this.db.select().from(notifications).where(eq19(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
@@ -15313,7 +15593,7 @@ var Notifier = class {
|
|
|
15313
15593
|
if (delivered > 0) {
|
|
15314
15594
|
this.db.update(doctorHealthState).set({ notifiedAt: now }).where(eq19(doctorHealthState.projectId, projectId)).run();
|
|
15315
15595
|
}
|
|
15316
|
-
|
|
15596
|
+
log20.info("health.notified", { projectId, event, status, code, subscribers: notifs.length, delivered });
|
|
15317
15597
|
return event;
|
|
15318
15598
|
}
|
|
15319
15599
|
/**
|
|
@@ -15334,7 +15614,7 @@ var Notifier = class {
|
|
|
15334
15614
|
if (check.status !== "ok" && check.status !== "fail") return null;
|
|
15335
15615
|
const project = this.db.select().from(projects).where(eq19(projects.id, projectId)).get();
|
|
15336
15616
|
if (!project) {
|
|
15337
|
-
|
|
15617
|
+
log20.error("project.not-found", { projectId, msg: "skipping site liveness notification" });
|
|
15338
15618
|
return null;
|
|
15339
15619
|
}
|
|
15340
15620
|
const previous = this.db.select().from(siteLivenessState).where(eq19(siteLivenessState.projectId, projectId)).get();
|
|
@@ -15388,7 +15668,7 @@ var Notifier = class {
|
|
|
15388
15668
|
if (event === "health.degraded" && delivered > 0) {
|
|
15389
15669
|
this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(eq19(siteLivenessState.projectId, projectId)).run();
|
|
15390
15670
|
}
|
|
15391
|
-
|
|
15671
|
+
log20.info("site-liveness.notified", { projectId, event, code: check.code, subscribers: notifs.length, delivered });
|
|
15392
15672
|
return event;
|
|
15393
15673
|
}
|
|
15394
15674
|
/**
|
|
@@ -15625,7 +15905,7 @@ var Notifier = class {
|
|
|
15625
15905
|
delivered += accepted;
|
|
15626
15906
|
}
|
|
15627
15907
|
}
|
|
15628
|
-
|
|
15908
|
+
log20.info("reviews.dispatched", {
|
|
15629
15909
|
projectId,
|
|
15630
15910
|
reviews: reviewsToSend.length,
|
|
15631
15911
|
ratingDrops: ratingsToSend.length,
|
|
@@ -15638,7 +15918,7 @@ var Notifier = class {
|
|
|
15638
15918
|
if (!thisRun) return [];
|
|
15639
15919
|
const completeness = measurementRunCompleteness(this.db, runId);
|
|
15640
15920
|
if (completeness.planned && !completeness.complete) {
|
|
15641
|
-
|
|
15921
|
+
log20.info("transitions.skipped-incomplete", {
|
|
15642
15922
|
runId,
|
|
15643
15923
|
executed: completeness.executed,
|
|
15644
15924
|
expected: completeness.expected
|
|
@@ -15730,23 +16010,23 @@ var Notifier = class {
|
|
|
15730
16010
|
const targetLabel = redactNotificationUrl(url).urlDisplay;
|
|
15731
16011
|
const targetCheck = await resolveWebhookTarget(url);
|
|
15732
16012
|
if (!targetCheck.ok) {
|
|
15733
|
-
|
|
16013
|
+
log20.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
|
|
15734
16014
|
this.logDelivery(projectId, notificationId, payload.event, "failed", `SSRF: ${targetCheck.message}`);
|
|
15735
16015
|
return false;
|
|
15736
16016
|
}
|
|
15737
|
-
|
|
16017
|
+
log20.info("webhook.send", { event: payload.event, url: targetLabel });
|
|
15738
16018
|
const maxRetries = 3;
|
|
15739
16019
|
const delays = [1e3, 4e3, 16e3];
|
|
15740
16020
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
15741
16021
|
try {
|
|
15742
16022
|
const response = await deliverWebhook(targetCheck.target, body, signingSecret);
|
|
15743
16023
|
if (response.status >= 200 && response.status < 300) {
|
|
15744
|
-
|
|
16024
|
+
log20.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
|
|
15745
16025
|
this.logDelivery(projectId, notificationId, payload.event, "sent", null);
|
|
15746
16026
|
return true;
|
|
15747
16027
|
}
|
|
15748
16028
|
const errorDetail = response.error ?? `HTTP ${response.status}`;
|
|
15749
|
-
|
|
16029
|
+
log20.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
|
|
15750
16030
|
if (attempt === maxRetries - 1) {
|
|
15751
16031
|
this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
|
|
15752
16032
|
}
|
|
@@ -15754,7 +16034,7 @@ var Notifier = class {
|
|
|
15754
16034
|
const errorDetail = describeError(err);
|
|
15755
16035
|
if (attempt === maxRetries - 1) {
|
|
15756
16036
|
this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
|
|
15757
|
-
|
|
16037
|
+
log20.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
|
|
15758
16038
|
}
|
|
15759
16039
|
}
|
|
15760
16040
|
if (attempt < maxRetries - 1) {
|
|
@@ -15779,7 +16059,7 @@ var Notifier = class {
|
|
|
15779
16059
|
|
|
15780
16060
|
// src/run-coordinator.ts
|
|
15781
16061
|
import { eq as eq20 } from "drizzle-orm";
|
|
15782
|
-
var
|
|
16062
|
+
var log21 = createLogger("RunCoordinator");
|
|
15783
16063
|
var RunCoordinator = class {
|
|
15784
16064
|
constructor(db, notifier, intelligenceService, onInsightsGenerated, onAeroEvent) {
|
|
15785
16065
|
this.db = db;
|
|
@@ -15795,16 +16075,31 @@ var RunCoordinator = class {
|
|
|
15795
16075
|
onAeroEvent;
|
|
15796
16076
|
/** Host wakeup only; durable completion receipts remain the delivery authority. */
|
|
15797
16077
|
onSentimentCompleted;
|
|
16078
|
+
/**
|
|
16079
|
+
* Host hook after a non-probe answer-visibility run finishes `completed` or
|
|
16080
|
+
* `partial`: the run stored new answers, so answer-derived competitor
|
|
16081
|
+
* aliases may have new evidence. The local server schedules a detection pass
|
|
16082
|
+
* off the completion path (`createCompetitorAutoAliasRunner`). Failures are
|
|
16083
|
+
* logged and never reach the other subscribers.
|
|
16084
|
+
*/
|
|
16085
|
+
onAnswersRecorded;
|
|
15798
16086
|
async onRunCompleted(runId, projectId, opts) {
|
|
15799
16087
|
const runRow = this.db.select().from(runs).where(eq20(runs.id, runId)).get();
|
|
15800
16088
|
const kind = runRow?.kind ?? RunKinds["answer-visibility"];
|
|
15801
16089
|
if (runRow?.trigger === RunTriggers.probe) {
|
|
15802
|
-
|
|
16090
|
+
log21.info("probe.skip-side-effects", { runId, projectId, kind });
|
|
15803
16091
|
return;
|
|
15804
16092
|
}
|
|
16093
|
+
if (kind === RunKinds["answer-visibility"] && (runRow?.status === RunStatuses.completed || runRow?.status === RunStatuses.partial) && this.onAnswersRecorded) {
|
|
16094
|
+
try {
|
|
16095
|
+
await this.onAnswersRecorded(runId, projectId);
|
|
16096
|
+
} catch (err) {
|
|
16097
|
+
log21.error("answers-recorded.failed", { runId, projectId, error: describeError(err) });
|
|
16098
|
+
}
|
|
16099
|
+
}
|
|
15805
16100
|
const completeness = measurementRunCompleteness(this.db, runId);
|
|
15806
16101
|
if (completeness.planned && !completeness.complete) {
|
|
15807
|
-
|
|
16102
|
+
log21.info("measurement.incomplete-skip-derived", {
|
|
15808
16103
|
runId,
|
|
15809
16104
|
projectId,
|
|
15810
16105
|
executed: completeness.executed,
|
|
@@ -15813,7 +16108,7 @@ var RunCoordinator = class {
|
|
|
15813
16108
|
try {
|
|
15814
16109
|
await this.notifier.onRunCompleted(runId, projectId);
|
|
15815
16110
|
} catch (err) {
|
|
15816
|
-
|
|
16111
|
+
log21.error("notifier.failed", { runId, error: describeError(err) });
|
|
15817
16112
|
}
|
|
15818
16113
|
return;
|
|
15819
16114
|
}
|
|
@@ -15821,7 +16116,7 @@ var RunCoordinator = class {
|
|
|
15821
16116
|
try {
|
|
15822
16117
|
await this.onSentimentCompleted(runId, projectId);
|
|
15823
16118
|
} catch {
|
|
15824
|
-
|
|
16119
|
+
log21.error("sentiment.wakeup-failed", { runId, projectId });
|
|
15825
16120
|
}
|
|
15826
16121
|
}
|
|
15827
16122
|
let insightCount = 0;
|
|
@@ -15838,12 +16133,12 @@ var RunCoordinator = class {
|
|
|
15838
16133
|
try {
|
|
15839
16134
|
await this.onInsightsGenerated(runId, projectId, result2);
|
|
15840
16135
|
} catch (err) {
|
|
15841
|
-
|
|
16136
|
+
log21.error("insight-webhook.failed", { runId, error: describeError(err) });
|
|
15842
16137
|
}
|
|
15843
16138
|
}
|
|
15844
16139
|
}
|
|
15845
16140
|
} catch (err) {
|
|
15846
|
-
|
|
16141
|
+
log21.error("intelligence.failed", { runId, error: describeError(err) });
|
|
15847
16142
|
}
|
|
15848
16143
|
} else if (kind === RunKinds["gbp-sync"]) {
|
|
15849
16144
|
try {
|
|
@@ -15856,22 +16151,22 @@ var RunCoordinator = class {
|
|
|
15856
16151
|
try {
|
|
15857
16152
|
await this.onInsightsGenerated(runId, projectId, analysisResultFromInsights(gbpInsights));
|
|
15858
16153
|
} catch (err) {
|
|
15859
|
-
|
|
16154
|
+
log21.error("gbp-insight-webhook.failed", { runId, error: describeError(err) });
|
|
15860
16155
|
}
|
|
15861
16156
|
}
|
|
15862
16157
|
} catch (err) {
|
|
15863
|
-
|
|
16158
|
+
log21.error("gbp-intelligence.failed", { runId, error: describeError(err) });
|
|
15864
16159
|
}
|
|
15865
16160
|
try {
|
|
15866
16161
|
await this.notifier.dispatchReviewAlerts(projectId);
|
|
15867
16162
|
} catch (err) {
|
|
15868
|
-
|
|
16163
|
+
log21.error("review-webhook.failed", { runId, error: describeError(err) });
|
|
15869
16164
|
}
|
|
15870
16165
|
}
|
|
15871
16166
|
try {
|
|
15872
16167
|
await (opts ? this.notifier.onRunCompleted(runId, projectId, opts) : this.notifier.onRunCompleted(runId, projectId));
|
|
15873
16168
|
} catch (err) {
|
|
15874
|
-
|
|
16169
|
+
log21.error("notifier.failed", { runId, error: describeError(err) });
|
|
15875
16170
|
}
|
|
15876
16171
|
if (this.onAeroEvent) {
|
|
15877
16172
|
try {
|
|
@@ -15884,7 +16179,7 @@ var RunCoordinator = class {
|
|
|
15884
16179
|
};
|
|
15885
16180
|
await this.onAeroEvent(ctx);
|
|
15886
16181
|
} catch (err) {
|
|
15887
|
-
|
|
16182
|
+
log21.error("aero.failed", { runId, error: describeError(err) });
|
|
15888
16183
|
}
|
|
15889
16184
|
}
|
|
15890
16185
|
}
|
|
@@ -16289,7 +16584,7 @@ function responseSchemasFor(operations) {
|
|
|
16289
16584
|
return schema ? [schema] : [];
|
|
16290
16585
|
});
|
|
16291
16586
|
}
|
|
16292
|
-
function
|
|
16587
|
+
function isRecord7(value) {
|
|
16293
16588
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
16294
16589
|
}
|
|
16295
16590
|
function resolve(schema, components) {
|
|
@@ -16322,7 +16617,7 @@ function render(schema, value, components, depth) {
|
|
|
16322
16617
|
}
|
|
16323
16618
|
let current = value;
|
|
16324
16619
|
for (const member of members(node)) current = render(member, current, components, depth + 1);
|
|
16325
|
-
if (Array.isArray(current) &&
|
|
16620
|
+
if (Array.isArray(current) && isRecord7(node.items)) {
|
|
16326
16621
|
const items = node.items;
|
|
16327
16622
|
let changed = false;
|
|
16328
16623
|
const out = current.map((item) => {
|
|
@@ -16332,9 +16627,9 @@ function render(schema, value, components, depth) {
|
|
|
16332
16627
|
});
|
|
16333
16628
|
return changed ? out : current;
|
|
16334
16629
|
}
|
|
16335
|
-
if (
|
|
16336
|
-
const properties =
|
|
16337
|
-
const extra =
|
|
16630
|
+
if (isRecord7(current)) {
|
|
16631
|
+
const properties = isRecord7(node.properties) ? node.properties : {};
|
|
16632
|
+
const extra = isRecord7(node.additionalProperties) ? node.additionalProperties : void 0;
|
|
16338
16633
|
let changed = false;
|
|
16339
16634
|
const out = {};
|
|
16340
16635
|
for (const [key, child] of Object.entries(current)) {
|
|
@@ -16380,7 +16675,7 @@ var MAX_PARTIAL_NOTES = 12;
|
|
|
16380
16675
|
function serializeResult(value) {
|
|
16381
16676
|
return JSON.stringify(value);
|
|
16382
16677
|
}
|
|
16383
|
-
function
|
|
16678
|
+
function isRecord8(value) {
|
|
16384
16679
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
16385
16680
|
}
|
|
16386
16681
|
function keptSummary(counts, cursors = {}) {
|
|
@@ -16400,7 +16695,7 @@ function noteCursor(owner, ownerPath, listPath, kept, total, notes, root = owner
|
|
|
16400
16695
|
}
|
|
16401
16696
|
function cursorForList(owner, ownerPath, listKey, root = owner) {
|
|
16402
16697
|
const prefix = ownerPath ? `${ownerPath}.` : "";
|
|
16403
|
-
if (listKey === "queries" &&
|
|
16698
|
+
if (listKey === "queries" && isRecord8(owner.queryPage) && owner.queryPage.nextCursor !== void 0 && owner.queryPage.nextCursor !== null) {
|
|
16404
16699
|
const page = owner.queryPage;
|
|
16405
16700
|
return {
|
|
16406
16701
|
path: `${prefix}queryPage.nextCursor`,
|
|
@@ -16466,7 +16761,7 @@ function partialLists(root) {
|
|
|
16466
16761
|
if (uncounted.length === 1) {
|
|
16467
16762
|
const index = uncounted[0];
|
|
16468
16763
|
const key = lists[index][0];
|
|
16469
|
-
const paged = cursorForList(object, path9, key, root) || typeof object.truncated === "boolean" || typeof object[`${key}Truncated`] === "boolean" || ["items", "rows", "results"].includes(key);
|
|
16764
|
+
const paged = object === root || cursorForList(object, path9, key, root) || typeof object.truncated === "boolean" || typeof object[`${key}Truncated`] === "boolean" || ["items", "rows", "results"].includes(key);
|
|
16470
16765
|
if (paged) counts[index] = bareTotal(numbers);
|
|
16471
16766
|
}
|
|
16472
16767
|
for (const [index, [key, rows]] of lists.entries()) {
|
|
@@ -16477,7 +16772,7 @@ function partialLists(root) {
|
|
|
16477
16772
|
if (count3.count <= rows.length) continue;
|
|
16478
16773
|
notes.push([label, `${rows.length} of ${count3.text}`]);
|
|
16479
16774
|
noted = true;
|
|
16480
|
-
} else if (ownFlag === true || object.truncated === true && typeof ownFlag !== "boolean" && (lists.length === 1 || rows.some(
|
|
16775
|
+
} else if (ownFlag === true || object.truncated === true && typeof ownFlag !== "boolean" && (lists.length === 1 || rows.some(isRecord8))) {
|
|
16481
16776
|
notes.push([label, `${rows.length} shown; the tool cut this list`]);
|
|
16482
16777
|
noted = true;
|
|
16483
16778
|
}
|
|
@@ -16486,17 +16781,17 @@ function partialLists(root) {
|
|
|
16486
16781
|
if (object.truncated === true && !noted) notes.push([path9 || "(root)", "the tool cut the lists here"]);
|
|
16487
16782
|
if (depth >= MAX_PARTIAL_DEPTH || notes.length >= MAX_PARTIAL_NOTES) return;
|
|
16488
16783
|
for (const [key, value] of Object.entries(object)) {
|
|
16489
|
-
if (!key.startsWith("__") &&
|
|
16784
|
+
if (!key.startsWith("__") && isRecord8(value)) visit(value, path9 ? `${path9}.${key}` : key, depth + 1);
|
|
16490
16785
|
}
|
|
16491
16786
|
};
|
|
16492
16787
|
visit(root, "", 1);
|
|
16493
16788
|
return notes.length > 0 ? Object.fromEntries(notes.slice(0, MAX_PARTIAL_NOTES)) : void 0;
|
|
16494
16789
|
}
|
|
16495
16790
|
function withPartialLists(value) {
|
|
16496
|
-
if (!
|
|
16791
|
+
if (!isRecord8(value) || typeof value.toJSON === "function") return value;
|
|
16497
16792
|
const hadNote = Object.hasOwn(value, PARTIAL_LISTS_KEY);
|
|
16498
16793
|
const notes = partialLists(value);
|
|
16499
|
-
if (!notes && !hadNote && value.nextAttemptCursor == null && !CURSOR_KEYS.some((key) => value[key] !== void 0 && value[key] !== null) && !Object.values(value).some((child) =>
|
|
16794
|
+
if (!notes && !hadNote && value.nextAttemptCursor == null && !CURSOR_KEYS.some((key) => value[key] !== void 0 && value[key] !== null) && !Object.values(value).some((child) => isRecord8(child) && CURSOR_KEYS.some((key) => child[key] !== void 0 && child[key] !== null))) return value;
|
|
16500
16795
|
const pagination = {};
|
|
16501
16796
|
const visit = (owner, path9, depth) => {
|
|
16502
16797
|
const lists = Object.entries(owner).filter((entry) => Array.isArray(entry[1]));
|
|
@@ -16506,11 +16801,11 @@ function withPartialLists(value) {
|
|
|
16506
16801
|
if (!cursor || key.startsWith("__") || Object.keys(pagination).length >= MAX_PARTIAL_NOTES) continue;
|
|
16507
16802
|
const label = path9 ? `${path9}.${key}` : key;
|
|
16508
16803
|
const total = cursor.total ?? ownListTotal(numbers, key) ?? (lists.length === 1 ? bareTotal(numbers) : void 0);
|
|
16509
|
-
const incomplete = owner.__truncated === true ||
|
|
16804
|
+
const incomplete = owner.__truncated === true || isRecord8(owner.__omittedRowsByField) && typeof owner.__omittedRowsByField[key] === "number";
|
|
16510
16805
|
pagination[label] = `showing ${rows.length}${total ? ` of ${total.text}` : " rows"}; ${incomplete ? `incomplete page: re-request the original cursor with a smaller ${cursor.limit}; the next cursor skips omitted rows` : `pass ${cursor.parameter} ${JSON.stringify(cursor.value)}`}`;
|
|
16511
16806
|
}
|
|
16512
16807
|
if (depth >= MAX_PARTIAL_DEPTH) return;
|
|
16513
|
-
for (const [key, child] of Object.entries(owner)) if (!key.startsWith("__") &&
|
|
16808
|
+
for (const [key, child] of Object.entries(owner)) if (!key.startsWith("__") && isRecord8(child)) visit(child, path9 ? `${path9}.${key}` : key, depth + 1);
|
|
16514
16809
|
};
|
|
16515
16810
|
visit(value, "", 1);
|
|
16516
16811
|
const { [PARTIAL_LISTS_KEY]: _previous, __pagination: _pagination, ...rest } = value;
|
|
@@ -16563,7 +16858,7 @@ function listTrimmer(lists) {
|
|
|
16563
16858
|
hadTruncated: Object.hasOwn(owner, "__truncated"),
|
|
16564
16859
|
priorTruncated: owner.__truncated,
|
|
16565
16860
|
previous,
|
|
16566
|
-
priorOmitted:
|
|
16861
|
+
priorOmitted: isRecord8(previous) ? previous : void 0
|
|
16567
16862
|
};
|
|
16568
16863
|
});
|
|
16569
16864
|
return (counts) => {
|
|
@@ -16599,12 +16894,12 @@ function collectCollections(root) {
|
|
|
16599
16894
|
if (Array.isArray(value)) {
|
|
16600
16895
|
let records = false;
|
|
16601
16896
|
for (const row of value) {
|
|
16602
|
-
if (!
|
|
16897
|
+
if (!isRecord8(row)) continue;
|
|
16603
16898
|
records = true;
|
|
16604
16899
|
visit(row, `${valuePath}[]`, true);
|
|
16605
16900
|
}
|
|
16606
16901
|
if (value.length > 0) found.push({ path: valuePath, nested, records, owner: object, key, rows: value });
|
|
16607
|
-
} else if (
|
|
16902
|
+
} else if (isRecord8(value)) {
|
|
16608
16903
|
visit(value, valuePath, nested);
|
|
16609
16904
|
}
|
|
16610
16905
|
}
|
|
@@ -16641,8 +16936,8 @@ function currentSummary(root, owned) {
|
|
|
16641
16936
|
counts.set(list.path, count3);
|
|
16642
16937
|
noteCursor(object, path9, list.path, value.length, list.rows.length, cursors, root);
|
|
16643
16938
|
}
|
|
16644
|
-
for (const row of value) if (
|
|
16645
|
-
} else if (
|
|
16939
|
+
for (const row of value) if (isRecord8(row)) visit(row, `${valuePath}[]`);
|
|
16940
|
+
} else if (isRecord8(value)) {
|
|
16646
16941
|
visit(value, valuePath);
|
|
16647
16942
|
}
|
|
16648
16943
|
}
|
|
@@ -16655,7 +16950,7 @@ function emptiedFloorChars(root) {
|
|
|
16655
16950
|
}
|
|
16656
16951
|
function trimNestedArrays(full) {
|
|
16657
16952
|
const copy = JSON.parse(full);
|
|
16658
|
-
if (!
|
|
16953
|
+
if (!isRecord8(copy)) return void 0;
|
|
16659
16954
|
copy.__truncated = true;
|
|
16660
16955
|
copy[TRUNCATION_SUMMARY_KEY] = keptSummary(/* @__PURE__ */ new Map());
|
|
16661
16956
|
if (emptiedFloorChars(copy) > MAX_TOOL_RESULT_CHARS) return void 0;
|
|
@@ -16726,7 +17021,7 @@ function trimNestedArrays(full) {
|
|
|
16726
17021
|
function keptRowTotal(serialized) {
|
|
16727
17022
|
const count3 = (value) => {
|
|
16728
17023
|
if (Array.isArray(value)) return value.length;
|
|
16729
|
-
if (!
|
|
17024
|
+
if (!isRecord8(value)) return 0;
|
|
16730
17025
|
return Object.entries(value).reduce((sum, [key, child]) => key.startsWith("__") ? sum : sum + count3(child), 0);
|
|
16731
17026
|
};
|
|
16732
17027
|
try {
|
|
@@ -16738,21 +17033,21 @@ function keptRowTotal(serialized) {
|
|
|
16738
17033
|
function hasOtherLists(value, primary) {
|
|
16739
17034
|
if (Array.isArray(value)) {
|
|
16740
17035
|
if (value !== primary && value.length > 0) return true;
|
|
16741
|
-
return value.some((row) => (
|
|
17036
|
+
return value.some((row) => (isRecord8(row) || Array.isArray(row)) && hasOtherLists(row, primary));
|
|
16742
17037
|
}
|
|
16743
|
-
return
|
|
17038
|
+
return isRecord8(value) && Object.values(value).some((child) => hasOtherLists(child, primary));
|
|
16744
17039
|
}
|
|
16745
17040
|
function projectionPath(path9) {
|
|
16746
17041
|
if (path9.length <= MAX_SUMMARY_PATH_CHARS) return path9;
|
|
16747
17042
|
const digest = createHash5("sha256").update(path9).digest("hex").slice(0, 12);
|
|
16748
17043
|
return `${truncateUtf16(path9, 48)}...#${digest}${path9.slice(-24)}`;
|
|
16749
17044
|
}
|
|
16750
|
-
function boundedProjection(full) {
|
|
17045
|
+
function boundedProjection(full, partialRows, reserve = 0) {
|
|
16751
17046
|
const value = JSON.parse(full);
|
|
16752
|
-
|
|
17047
|
+
let summary = {
|
|
16753
17048
|
droppedKeys: [],
|
|
16754
17049
|
keptItems: {},
|
|
16755
|
-
projection: "Only complete array rows and marked partial string prefixes are shown; omitted fields are not evidence. Never follow a cursor from a cut page; re-request the original cursor with a smaller limit."
|
|
17050
|
+
projection: partialRows ? "Array rows are complete unless slicedKeys or droppedKeys name a field inside one; sliced strings are partial prefixes and omitted fields are not evidence. Never follow a cursor from a cut page; re-request the original cursor with a smaller limit." : "Only complete array rows and marked partial string prefixes are shown; omitted fields are not evidence. Never follow a cursor from a cut page; re-request the original cursor with a smaller limit."
|
|
16756
17051
|
};
|
|
16757
17052
|
const skippedMetadata = () => {
|
|
16758
17053
|
summary.moreMetadataEntries = (summary.moreMetadataEntries ?? 0) + 1;
|
|
@@ -16778,6 +17073,7 @@ function boundedProjection(full) {
|
|
|
16778
17073
|
}
|
|
16779
17074
|
if (dropped > summary.droppedKeys.length) summary.moreDroppedKeys = dropped - summary.droppedKeys.length;
|
|
16780
17075
|
};
|
|
17076
|
+
const cutOwners = /* @__PURE__ */ new Set();
|
|
16781
17077
|
const project = (input, budget, path9, owner, ownerPath = "") => {
|
|
16782
17078
|
if (Array.isArray(input)) {
|
|
16783
17079
|
const rows = [];
|
|
@@ -16785,16 +17081,31 @@ function boundedProjection(full) {
|
|
|
16785
17081
|
for (const row of input) {
|
|
16786
17082
|
const text = serializeResult(row);
|
|
16787
17083
|
const cost = text.length + (rows.length > 0 ? 1 : 0);
|
|
16788
|
-
if (used2 + cost
|
|
16789
|
-
|
|
16790
|
-
|
|
17084
|
+
if (used2 + cost <= budget) {
|
|
17085
|
+
rows.push(text);
|
|
17086
|
+
used2 += cost;
|
|
17087
|
+
continue;
|
|
17088
|
+
}
|
|
17089
|
+
if (partialRows && (rows.length === 0 || text.length > MAX_TOOL_RESULT_CHARS)) {
|
|
17090
|
+
const before = { summary: serializeResult(summary), dropped };
|
|
17091
|
+
const partial = project(row, budget - used2 - (rows.length > 0 ? 1 : 0), `${path9 || "items"}[${rows.length}]`);
|
|
17092
|
+
if (partial !== void 0 && partial !== "{}" && partial !== "[]") {
|
|
17093
|
+
partialRows.set(path9 || "items", rows.length);
|
|
17094
|
+
rows.push(partial);
|
|
17095
|
+
} else {
|
|
17096
|
+
summary = JSON.parse(before.summary);
|
|
17097
|
+
dropped = before.dropped;
|
|
17098
|
+
}
|
|
17099
|
+
}
|
|
17100
|
+
break;
|
|
16791
17101
|
}
|
|
16792
17102
|
if (rows.length < input.length) {
|
|
17103
|
+
if (partialRows && owner) cutOwners.add(owner);
|
|
16793
17104
|
record2(summary.keptItems, path9 || "items", `${rows.length} of ${input.length}`);
|
|
16794
17105
|
if (rows.length === 0) omit(path9 || "items");
|
|
16795
17106
|
if (owner) {
|
|
16796
17107
|
const cursors = {};
|
|
16797
|
-
noteCursor(owner, ownerPath, path9, rows.length, input.length, cursors,
|
|
17108
|
+
noteCursor(owner, ownerPath, path9, rows.length, input.length, cursors, isRecord8(value) ? value : owner);
|
|
16798
17109
|
for (const [cursor, note] of Object.entries(cursors)) {
|
|
16799
17110
|
record2(summary.cursors ??= {}, cursor, note.replace(path9, projectionPath(path9)));
|
|
16800
17111
|
}
|
|
@@ -16802,7 +17113,7 @@ function boundedProjection(full) {
|
|
|
16802
17113
|
}
|
|
16803
17114
|
return `[${rows.join(",")}]`;
|
|
16804
17115
|
}
|
|
16805
|
-
if (!
|
|
17116
|
+
if (!isRecord8(input)) {
|
|
16806
17117
|
const text = serializeResult(input);
|
|
16807
17118
|
if (text.length <= budget) return text;
|
|
16808
17119
|
if (typeof input === "string" && budget > 2) {
|
|
@@ -16813,9 +17124,8 @@ function boundedProjection(full) {
|
|
|
16813
17124
|
omit(path9 || "(root)");
|
|
16814
17125
|
return void 0;
|
|
16815
17126
|
}
|
|
16816
|
-
const
|
|
16817
|
-
const
|
|
16818
|
-
fields.sort((a, b) => priority(a) - priority(b));
|
|
17127
|
+
const priority = ([key, child]) => key === "queryPage" && isRecord8(child) ? 0 : typeof child === "string" && child.length > budget ? 2 : typeof child !== "object" || child === null ? 0 : partialRows && serializeResult(child).length > budget ? 2 : 1;
|
|
17128
|
+
const fields = Object.entries(input).filter(([key]) => !key.startsWith("__")).map((field) => ({ field, rank: priority(field) })).sort((a, b) => a.rank - b.rank).map(({ field }) => field);
|
|
16819
17129
|
const shown2 = [];
|
|
16820
17130
|
let used = 2;
|
|
16821
17131
|
for (const [key, child] of fields) {
|
|
@@ -16831,17 +17141,40 @@ function boundedProjection(full) {
|
|
|
16831
17141
|
shown2.push(prefix + text);
|
|
16832
17142
|
used += prefix.length + text.length + (shown2.length > 1 ? 1 : 0);
|
|
16833
17143
|
}
|
|
17144
|
+
if (path9 && cutOwners.has(input)) shown2.push('"__truncated":true');
|
|
16834
17145
|
return `{${shown2.join(",")}}`;
|
|
16835
17146
|
};
|
|
16836
|
-
const projected = project(value, MAX_TOOL_RESULT_CHARS - PROJECTION_SUMMARY_CHARS, "");
|
|
17147
|
+
const projected = project(value, MAX_TOOL_RESULT_CHARS - PROJECTION_SUMMARY_CHARS - reserve, "");
|
|
16837
17148
|
const shown = projected === void 0 ? {} : JSON.parse(projected);
|
|
16838
|
-
const output =
|
|
16839
|
-
|
|
16840
|
-
|
|
16841
|
-
|
|
17149
|
+
const output = isRecord8(shown) ? shown : { items: shown };
|
|
17150
|
+
const result2 = { ...output, __truncated: true, [TRUNCATION_SUMMARY_KEY]: summary };
|
|
17151
|
+
return serializeResult(partialRows ? withPartialLists(result2) : result2);
|
|
17152
|
+
}
|
|
17153
|
+
function rowsShown(cut, path9) {
|
|
17154
|
+
if (path9.includes("[")) return Infinity;
|
|
17155
|
+
const list = path9.split(".").reduce((owner, key) => isRecord8(owner) ? owner[key] : void 0, cut);
|
|
17156
|
+
return Array.isArray(list) ? list.length : 0;
|
|
17157
|
+
}
|
|
17158
|
+
function withPartialRows(full, whole) {
|
|
17159
|
+
const rescued = /* @__PURE__ */ new Map();
|
|
17160
|
+
let partial = boundedProjection(full, rescued);
|
|
17161
|
+
if (!fitsCap(partial)) {
|
|
17162
|
+
const { [PARTIAL_LISTS_KEY]: lists, __pagination: pagination } = JSON.parse(partial);
|
|
17163
|
+
rescued.clear();
|
|
17164
|
+
partial = boundedProjection(full, rescued, serializeResult({ lists, pagination }).length);
|
|
17165
|
+
}
|
|
17166
|
+
if (!fitsCap(partial)) return whole;
|
|
17167
|
+
const cut = JSON.parse(whole);
|
|
17168
|
+
return [...rescued].some(([path9, index]) => rowsShown(cut, path9) <= index) ? partial : whole;
|
|
17169
|
+
}
|
|
17170
|
+
function truncateToolResult(details, options = {}) {
|
|
16842
17171
|
const annotated = withPartialLists(details);
|
|
16843
17172
|
const full = serializeResult(annotated);
|
|
16844
17173
|
if (full.length <= MAX_TOOL_RESULT_CHARS) return full;
|
|
17174
|
+
const whole = truncateWholeRows(details, full);
|
|
17175
|
+
return options.partialRows ? withPartialRows(full, whole) : whole;
|
|
17176
|
+
}
|
|
17177
|
+
function truncateWholeRows(details, full) {
|
|
16845
17178
|
if (full.length > MAX_STRUCTURED_TRUNCATION_CHARS) return boundedProjection(full);
|
|
16846
17179
|
if (Array.isArray(details)) {
|
|
16847
17180
|
const render2 = (rows) => serializeResult({
|
|
@@ -16961,6 +17294,12 @@ var AERO_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
|
|
|
16961
17294
|
// start paid classifier work and remain operator actions.
|
|
16962
17295
|
"canonry_sentiment_configure",
|
|
16963
17296
|
"canonry_sentiment_backfill",
|
|
17297
|
+
// Applying answer-derived competitor names changes the measured competitor
|
|
17298
|
+
// identity and restates stored history. Aero may preview detection
|
|
17299
|
+
// (`canonry_competitors_auto_aliases_detect`); applying stays an operator action.
|
|
17300
|
+
// The project writes that set the mode refuse a change instead; see
|
|
17301
|
+
// `refuseCompetitorAutoAliasModeChange`.
|
|
17302
|
+
CanonryMcpToolNames.canonry_competitors_auto_aliases_apply,
|
|
16964
17303
|
CanonryMcpToolNames.canonry_agent_clear,
|
|
16965
17304
|
CanonryMcpToolNames.canonry_agent_conversations_new,
|
|
16966
17305
|
CanonryMcpToolNames.canonry_agent_conversations_resume,
|
|
@@ -16989,9 +17328,48 @@ function refuseManagedSweepCancel(tool, ctx) {
|
|
|
16989
17328
|
}
|
|
16990
17329
|
};
|
|
16991
17330
|
}
|
|
17331
|
+
var COMPETITOR_AUTO_ALIAS_MODE_REFUSAL = "Aero cannot change a project's competitorAutoAliases mode. Omit competitorAutoAliases to keep the stored mode, or ask the operator to change it.";
|
|
17332
|
+
var COMPETITOR_AUTO_ALIAS_MODE_WRITES = /* @__PURE__ */ new Set([
|
|
17333
|
+
CanonryMcpToolNames.canonry_project_upsert,
|
|
17334
|
+
CanonryMcpToolNames.canonry_apply_config
|
|
17335
|
+
]);
|
|
17336
|
+
function requestedCompetitorAutoAliasMode(toolName, params, ctx) {
|
|
17337
|
+
if (!isRecord8(params)) return { project: void 0, mode: void 0 };
|
|
17338
|
+
if (toolName === CanonryMcpToolNames.canonry_project_upsert) {
|
|
17339
|
+
const request = isRecord8(params.request) ? params.request : {};
|
|
17340
|
+
return { project: ctx.projectName, mode: request.competitorAutoAliases };
|
|
17341
|
+
}
|
|
17342
|
+
const config = isRecord8(params.config) ? params.config : {};
|
|
17343
|
+
const metadata2 = isRecord8(config.metadata) ? config.metadata : {};
|
|
17344
|
+
const spec = isRecord8(config.spec) ? config.spec : {};
|
|
17345
|
+
return { project: metadata2.name, mode: spec.competitorAutoAliases };
|
|
17346
|
+
}
|
|
17347
|
+
async function storedCompetitorAutoAliasMode(client, project) {
|
|
17348
|
+
if (typeof project !== "string") return DEFAULT_COMPETITOR_AUTO_ALIAS_MODE;
|
|
17349
|
+
try {
|
|
17350
|
+
return (await client.getProject(project)).competitorAutoAliases ?? DEFAULT_COMPETITOR_AUTO_ALIAS_MODE;
|
|
17351
|
+
} catch (err) {
|
|
17352
|
+
if (err instanceof CliError && err.code === "NOT_FOUND") return DEFAULT_COMPETITOR_AUTO_ALIAS_MODE;
|
|
17353
|
+
throw err;
|
|
17354
|
+
}
|
|
17355
|
+
}
|
|
17356
|
+
function refuseCompetitorAutoAliasModeChange(tool, ctx) {
|
|
17357
|
+
const write = tool.execute;
|
|
17358
|
+
return {
|
|
17359
|
+
...tool,
|
|
17360
|
+
execute: async (toolCallId, params, ...rest) => {
|
|
17361
|
+
const { project, mode } = requestedCompetitorAutoAliasMode(tool.name, params, ctx);
|
|
17362
|
+
if (mode !== void 0 && mode !== await storedCompetitorAutoAliasMode(ctx.client, project)) {
|
|
17363
|
+
throw new Error(COMPETITOR_AUTO_ALIAS_MODE_REFUSAL);
|
|
17364
|
+
}
|
|
17365
|
+
return write(toolCallId, params, ...rest);
|
|
17366
|
+
}
|
|
17367
|
+
};
|
|
17368
|
+
}
|
|
16992
17369
|
function buildMcpAgentTools(registry, ctx, opts = {}) {
|
|
16993
17370
|
return registry.filter((tool) => !AERO_EXCLUDED_MCP_TOOLS.has(tool.name)).filter((tool) => opts.includeNames ? opts.includeNames.has(tool.name) : true).filter((tool) => opts.readOnly ? tool.access === "read" : true).filter((tool) => opts.managedSweeps ? !AERO_MANAGED_SWEEP_MCP_TOOLS.has(tool.name) : true).map((tool) => {
|
|
16994
17371
|
const agentTool = mcpToAgentTool(tool, ctx);
|
|
17372
|
+
if (COMPETITOR_AUTO_ALIAS_MODE_WRITES.has(tool.name)) return refuseCompetitorAutoAliasModeChange(agentTool, ctx);
|
|
16995
17373
|
return opts.managedSweeps && tool.name === CanonryMcpToolNames.canonry_run_cancel ? refuseManagedSweepCancel(agentTool, ctx) : agentTool;
|
|
16996
17374
|
});
|
|
16997
17375
|
}
|
|
@@ -17322,11 +17700,11 @@ function recordLlmUsageEvent(args) {
|
|
|
17322
17700
|
var AERO_MEMORY_SEPARATOR = "\n\n---\n\n";
|
|
17323
17701
|
var AERO_MEMORY_BLOCK_PREFIX = `${AERO_MEMORY_SEPARATOR}<memory>
|
|
17324
17702
|
`;
|
|
17325
|
-
function
|
|
17703
|
+
function isRecord9(value) {
|
|
17326
17704
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
17327
17705
|
}
|
|
17328
17706
|
function isTextSystemBlock(value) {
|
|
17329
|
-
return
|
|
17707
|
+
return isRecord9(value) && value.type === "text" && typeof value.text === "string";
|
|
17330
17708
|
}
|
|
17331
17709
|
function splitAeroHydratedSystemPrompt(systemPrompt) {
|
|
17332
17710
|
const idx = systemPrompt.lastIndexOf(AERO_MEMORY_BLOCK_PREFIX);
|
|
@@ -17338,7 +17716,7 @@ function splitAeroHydratedSystemPrompt(systemPrompt) {
|
|
|
17338
17716
|
}
|
|
17339
17717
|
function splitAeroAnthropicSystemCachePayload(payload, model) {
|
|
17340
17718
|
if (model.api !== "anthropic-messages") return void 0;
|
|
17341
|
-
if (!
|
|
17719
|
+
if (!isRecord9(payload) || !Array.isArray(payload.system)) return void 0;
|
|
17342
17720
|
let changed = false;
|
|
17343
17721
|
const nextSystem = [];
|
|
17344
17722
|
for (const block of payload.system) {
|
|
@@ -18098,7 +18476,7 @@ function resolveSessionProviderAndModel(config, opts) {
|
|
|
18098
18476
|
import { Type as Type6 } from "@earendil-works/pi-ai";
|
|
18099
18477
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
18100
18478
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
18101
|
-
var
|
|
18479
|
+
var log22 = createLogger("RemoteMcp");
|
|
18102
18480
|
var AERO_EXCLUDED_MCP_TOOLS2 = /* @__PURE__ */ new Set([]);
|
|
18103
18481
|
async function connectStreamableHttp(server) {
|
|
18104
18482
|
const transport = new StreamableHTTPClientTransport(new URL(server.url), {
|
|
@@ -18141,7 +18519,8 @@ function adaptRemoteTool(client, tool) {
|
|
|
18141
18519
|
}
|
|
18142
18520
|
}
|
|
18143
18521
|
return {
|
|
18144
|
-
|
|
18522
|
+
// A fetched document can be one row larger than the cap; show it in part rather than not at all.
|
|
18523
|
+
content: [{ type: "text", text: truncateToolResult(shown, { partialRows: true }) }],
|
|
18145
18524
|
details: result2,
|
|
18146
18525
|
isError: isRemoteToolError(result2)
|
|
18147
18526
|
};
|
|
@@ -18165,7 +18544,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
18165
18544
|
try {
|
|
18166
18545
|
client = await connect(server);
|
|
18167
18546
|
} catch (err) {
|
|
18168
|
-
|
|
18547
|
+
log22.error("external-mcp.connect-failed", {
|
|
18169
18548
|
label,
|
|
18170
18549
|
error: describeError(err)
|
|
18171
18550
|
});
|
|
@@ -18175,7 +18554,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
18175
18554
|
try {
|
|
18176
18555
|
listed = await client.listTools();
|
|
18177
18556
|
} catch (err) {
|
|
18178
|
-
|
|
18557
|
+
log22.error("external-mcp.list-failed", {
|
|
18179
18558
|
label,
|
|
18180
18559
|
error: describeError(err)
|
|
18181
18560
|
});
|
|
@@ -18186,7 +18565,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
18186
18565
|
if (!isReadOnly(tool)) continue;
|
|
18187
18566
|
tools.push(adaptRemoteTool(client, tool));
|
|
18188
18567
|
}
|
|
18189
|
-
|
|
18568
|
+
log22.info("external-mcp.loaded", {
|
|
18190
18569
|
label,
|
|
18191
18570
|
discovered: listed.tools.length,
|
|
18192
18571
|
adopted: tools.length
|
|
@@ -18494,7 +18873,7 @@ async function compactMessages(args) {
|
|
|
18494
18873
|
}
|
|
18495
18874
|
|
|
18496
18875
|
// src/agent/session-registry.ts
|
|
18497
|
-
var
|
|
18876
|
+
var log23 = createLogger("SessionRegistry");
|
|
18498
18877
|
var MAX_HYDRATE_NOTES = 20;
|
|
18499
18878
|
var MAX_HYDRATE_BYTES = 32 * 1024;
|
|
18500
18879
|
function escapeMemoryFragment(value) {
|
|
@@ -18553,7 +18932,7 @@ var SessionRegistry = class {
|
|
|
18553
18932
|
if (!this.externalToolsPromise) {
|
|
18554
18933
|
this.externalToolsPromise = loadExternalMcpTools(this.opts.config.externalMcpServers).catch(
|
|
18555
18934
|
(err) => {
|
|
18556
|
-
|
|
18935
|
+
log23.error("external-mcp.load-failed", {
|
|
18557
18936
|
error: describeError(err)
|
|
18558
18937
|
});
|
|
18559
18938
|
return [];
|
|
@@ -18827,13 +19206,13 @@ ${lines.join("\n")}
|
|
|
18827
19206
|
agent.state.messages = result2.messages;
|
|
18828
19207
|
setAeroSystemPrompt(agent, this.buildHydratedSystemPrompt(projectId, row.systemPrompt));
|
|
18829
19208
|
this.save(projectName);
|
|
18830
|
-
|
|
19209
|
+
log23.info("compaction.completed", {
|
|
18831
19210
|
projectName,
|
|
18832
19211
|
removedCount: result2.removedCount,
|
|
18833
19212
|
summaryBytes: Buffer.byteLength(result2.summary, "utf8")
|
|
18834
19213
|
});
|
|
18835
19214
|
} catch (err) {
|
|
18836
|
-
|
|
19215
|
+
log23.error("compaction.failed", {
|
|
18837
19216
|
projectName,
|
|
18838
19217
|
error: describeError(err)
|
|
18839
19218
|
});
|
|
@@ -18984,7 +19363,7 @@ ${lines.join("\n")}
|
|
|
18984
19363
|
await agent.prompt(msgs);
|
|
18985
19364
|
this.save(projectName);
|
|
18986
19365
|
} catch (err) {
|
|
18987
|
-
|
|
19366
|
+
log23.error("drain.failed", {
|
|
18988
19367
|
projectName,
|
|
18989
19368
|
error: describeError(err)
|
|
18990
19369
|
});
|
|
@@ -20150,7 +20529,7 @@ function formatAuditFactorScore(factor) {
|
|
|
20150
20529
|
}
|
|
20151
20530
|
|
|
20152
20531
|
// src/snapshot-service.ts
|
|
20153
|
-
var
|
|
20532
|
+
var log24 = createLogger("Snapshot");
|
|
20154
20533
|
var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "muse", "local"];
|
|
20155
20534
|
var SNAPSHOT_QUERY_COUNT = 6;
|
|
20156
20535
|
var SnapshotService = class {
|
|
@@ -20247,7 +20626,7 @@ var SnapshotService = class {
|
|
|
20247
20626
|
return mapAuditReport(report);
|
|
20248
20627
|
} catch (err) {
|
|
20249
20628
|
const message = describeError(err);
|
|
20250
|
-
|
|
20629
|
+
log24.warn("audit.failed", { homepageUrl, error: message });
|
|
20251
20630
|
return {
|
|
20252
20631
|
url: homepageUrl,
|
|
20253
20632
|
finalUrl: homepageUrl,
|
|
@@ -20276,7 +20655,7 @@ var SnapshotService = class {
|
|
|
20276
20655
|
queries: parsedQueries
|
|
20277
20656
|
};
|
|
20278
20657
|
} catch (err) {
|
|
20279
|
-
|
|
20658
|
+
log24.warn("profile.generation-failed", {
|
|
20280
20659
|
domain: ctx.domain,
|
|
20281
20660
|
provider: ctx.analysisProvider.adapter.name,
|
|
20282
20661
|
error: describeError(err)
|
|
@@ -20419,7 +20798,7 @@ var SnapshotService = class {
|
|
|
20419
20798
|
recommendedActions: uniqueStrings2(parsed.recommendedActions ?? []).slice(0, 4)
|
|
20420
20799
|
};
|
|
20421
20800
|
} catch (err) {
|
|
20422
|
-
|
|
20801
|
+
log24.warn("response.analysis-failed", {
|
|
20423
20802
|
provider: ctx.analysisProvider.adapter.name,
|
|
20424
20803
|
error: describeError(err)
|
|
20425
20804
|
});
|
|
@@ -20712,9 +21091,9 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
20712
21091
|
reserved = run.totalQueries;
|
|
20713
21092
|
reservation = { scope, period };
|
|
20714
21093
|
const rows = db.select().from(researchRunQueries).where(eq27(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
|
|
20715
|
-
const competitorRows = db.select(
|
|
21094
|
+
const competitorRows = db.select(competitorIdentityColumns).from(competitors).where(eq27(competitors.projectId, projectId)).orderBy(competitors.domain).all();
|
|
20716
21095
|
const competitorDomains = competitorRows.map((row) => row.domain);
|
|
20717
|
-
const competitorAliases = new Map(competitorRows.
|
|
21096
|
+
const competitorAliases = new Map(competitorRows.map((row) => [row.domain, competitorIdentityAliases(row)]).filter(([, aliases]) => aliases.length > 0));
|
|
20718
21097
|
const domains = effectiveDomains(project);
|
|
20719
21098
|
const brands = effectiveBrandNames(project);
|
|
20720
21099
|
const config = { ...provider.config, model: run.resolvedModel };
|
|
@@ -21047,7 +21426,7 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
21047
21426
|
// src/server.ts
|
|
21048
21427
|
var _require3 = createRequire3(import.meta.url);
|
|
21049
21428
|
var { version: PKG_VERSION2 } = _require3("../package.json");
|
|
21050
|
-
var
|
|
21429
|
+
var log25 = createLogger("Server");
|
|
21051
21430
|
var runtimeStartupByServer = /* @__PURE__ */ new WeakMap();
|
|
21052
21431
|
function waitForServerRuntimeStartup(app) {
|
|
21053
21432
|
const startup = runtimeStartupByServer.get(app);
|
|
@@ -21253,7 +21632,7 @@ function applyLegacyCredentials(rows, config) {
|
|
|
21253
21632
|
}
|
|
21254
21633
|
if (migratedGoogle > 0) {
|
|
21255
21634
|
saveConfigPatch({ google: config.google });
|
|
21256
|
-
|
|
21635
|
+
log25.info("credentials.migrated", { type: "google", count: migratedGoogle });
|
|
21257
21636
|
}
|
|
21258
21637
|
let migratedGa4 = 0;
|
|
21259
21638
|
for (const row of rows.ga4) {
|
|
@@ -21271,7 +21650,7 @@ function applyLegacyCredentials(rows, config) {
|
|
|
21271
21650
|
}
|
|
21272
21651
|
if (migratedGa4 > 0) {
|
|
21273
21652
|
saveConfigPatch({ ga4: config.ga4 });
|
|
21274
|
-
|
|
21653
|
+
log25.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
|
|
21275
21654
|
}
|
|
21276
21655
|
}
|
|
21277
21656
|
function isLoopbackBindHost(host) {
|
|
@@ -21508,11 +21887,11 @@ async function createServer(opts) {
|
|
|
21508
21887
|
applyLegacyCredentials(legacyRows, opts.config);
|
|
21509
21888
|
dropLegacyCredentialColumns(opts.db);
|
|
21510
21889
|
} catch (err) {
|
|
21511
|
-
|
|
21890
|
+
log25.warn("credentials.migration.failed", {
|
|
21512
21891
|
error: describeError(err)
|
|
21513
21892
|
});
|
|
21514
21893
|
}
|
|
21515
|
-
|
|
21894
|
+
log25.info("providers.configured", {
|
|
21516
21895
|
providers: Object.keys(providers).filter((k) => {
|
|
21517
21896
|
const p = providers[k];
|
|
21518
21897
|
return p?.apiKey || p?.baseUrl || p?.vertexProject;
|
|
@@ -21527,7 +21906,7 @@ async function createServer(opts) {
|
|
|
21527
21906
|
}
|
|
21528
21907
|
}
|
|
21529
21908
|
for (const name of providersWithUnsupportedBatch(providers, (provider) => adapterMap[provider])) {
|
|
21530
|
-
|
|
21909
|
+
log25.warn("provider.batch.unsupported", {
|
|
21531
21910
|
provider: name,
|
|
21532
21911
|
message: `providers.${name}.batch.enabled is true, but the ${name} adapter has no batch API; its sweeps run sync.`
|
|
21533
21912
|
});
|
|
@@ -22599,7 +22978,7 @@ async function createServer(opts) {
|
|
|
22599
22978
|
const sentimentPoller = createSentimentPoller(
|
|
22600
22979
|
() => sentimentWorker.tick(),
|
|
22601
22980
|
() => {
|
|
22602
|
-
|
|
22981
|
+
log25.error("sentiment.worker-failed", { reason: "Stored sentiment worker could not finish its tick." });
|
|
22603
22982
|
}
|
|
22604
22983
|
);
|
|
22605
22984
|
const pollSentiment = sentimentPoller.poll;
|
|
@@ -22607,6 +22986,110 @@ async function createServer(opts) {
|
|
|
22607
22986
|
runCoordinator.onSentimentCompleted = async () => {
|
|
22608
22987
|
pollSentiment();
|
|
22609
22988
|
};
|
|
22989
|
+
const answerFieldRecomputes = createAnswerFieldRecomputeQueue({ db: opts.db });
|
|
22990
|
+
const recomputeDebts = /* @__PURE__ */ new Map();
|
|
22991
|
+
let recomputeSequence = 0;
|
|
22992
|
+
const markAnswerFieldRecompute = (projectId, full) => {
|
|
22993
|
+
opts.db.update(projects).set({ answerFieldsRecompute: full ? "full" : sql11`CASE WHEN ${projects.answerFieldsRecompute} = 'full' THEN 'full' ELSE 'competitors' END` }).where(eq29(projects.id, projectId)).run();
|
|
22994
|
+
};
|
|
22995
|
+
const requestAnswerFieldRecompute = (projectId, projectName, scope, reason) => {
|
|
22996
|
+
const sequence = ++recomputeSequence;
|
|
22997
|
+
const debt = recomputeDebts.get(projectId) ?? { latest: 0, latestFull: 0 };
|
|
22998
|
+
debt.latest = sequence;
|
|
22999
|
+
if (scope.full) debt.latestFull = sequence;
|
|
23000
|
+
recomputeDebts.set(projectId, debt);
|
|
23001
|
+
try {
|
|
23002
|
+
markAnswerFieldRecompute(projectId, scope.full === true);
|
|
23003
|
+
} catch (err) {
|
|
23004
|
+
app.log.error({ err, projectId }, "could not mark the owed answer field recompute");
|
|
23005
|
+
}
|
|
23006
|
+
answerFieldRecomputes.request(projectId, scope).then(
|
|
23007
|
+
(pass) => {
|
|
23008
|
+
if (pass.full && debt.latestFull <= sequence) debt.latestFull = 0;
|
|
23009
|
+
try {
|
|
23010
|
+
if (debt.latest === sequence && debt.latestFull === 0) {
|
|
23011
|
+
opts.db.update(projects).set({ answerFieldsRecompute: null }).where(eq29(projects.id, projectId)).run();
|
|
23012
|
+
if (recomputeDebts.get(projectId) === debt) recomputeDebts.delete(projectId);
|
|
23013
|
+
} else if (debt.latestFull === 0) {
|
|
23014
|
+
opts.db.update(projects).set({ answerFieldsRecompute: "competitors" }).where(and23(eq29(projects.id, projectId), eq29(projects.answerFieldsRecompute, "full"))).run();
|
|
23015
|
+
}
|
|
23016
|
+
} catch (err) {
|
|
23017
|
+
app.log.error({ err, projectId }, "could not clear the owed answer field recompute");
|
|
23018
|
+
}
|
|
23019
|
+
app.log.info(
|
|
23020
|
+
{ projectId, projectName, reason, full: pass.full, ...pass.result },
|
|
23021
|
+
"identity changed: recomputed stored answer fields on historical snapshots"
|
|
23022
|
+
);
|
|
23023
|
+
},
|
|
23024
|
+
(err) => {
|
|
23025
|
+
app.log.error(
|
|
23026
|
+
{ err, projectId, projectName, reason },
|
|
23027
|
+
"identity-triggered answer field recompute failed"
|
|
23028
|
+
);
|
|
23029
|
+
}
|
|
23030
|
+
);
|
|
23031
|
+
};
|
|
23032
|
+
const resumeAnswerFieldRecomputes = () => {
|
|
23033
|
+
const owed = opts.db.select({ id: projects.id, name: projects.name, scope: projects.answerFieldsRecompute }).from(projects).where(isNotNull4(projects.answerFieldsRecompute)).all();
|
|
23034
|
+
for (const project of owed) {
|
|
23035
|
+
requestAnswerFieldRecompute(project.id, project.name, { full: project.scope === "full" }, "resume-after-restart");
|
|
23036
|
+
}
|
|
23037
|
+
};
|
|
23038
|
+
const recomputeCompetitorFields = (projectId, projectName) => {
|
|
23039
|
+
requestAnswerFieldRecompute(projectId, projectName, {}, "competitor-names");
|
|
23040
|
+
};
|
|
23041
|
+
const competitorAutoAliases = createCompetitorAutoAliasRunner({
|
|
23042
|
+
db: opts.db,
|
|
23043
|
+
readAnchors: extractStoredAnswerAnchors,
|
|
23044
|
+
onNamesChanged: recomputeCompetitorFields
|
|
23045
|
+
});
|
|
23046
|
+
const competitorAutoAliasReasons = /* @__PURE__ */ new Map();
|
|
23047
|
+
const competitorAutoAliasPasses = createProjectPassQueue(async (projectId) => {
|
|
23048
|
+
await yieldToEventLoop4();
|
|
23049
|
+
const reason = (competitorAutoAliasReasons.get(projectId) ?? []).join(",");
|
|
23050
|
+
competitorAutoAliasReasons.delete(projectId);
|
|
23051
|
+
const project = opts.db.select().from(projects).where(eq29(projects.id, projectId)).get();
|
|
23052
|
+
if (!project || project.competitorAutoAliases === "off") return;
|
|
23053
|
+
if (project.competitorAutoAliases === "apply") {
|
|
23054
|
+
await competitorAutoAliases.request(projectId, reason).catch(() => void 0);
|
|
23055
|
+
return;
|
|
23056
|
+
}
|
|
23057
|
+
const detection = await previewCompetitorAutoAliases(opts.db, project, { readAnchors: extractStoredAnswerAnchors });
|
|
23058
|
+
const wouldAdd = detection.competitors.flatMap((competitor) => competitor.added.map((name) => `${competitor.domain}: ${name}`));
|
|
23059
|
+
const wouldRemove = detection.competitors.flatMap((competitor) => competitor.removed.map((entry) => `${competitor.domain}: ${entry.name}`));
|
|
23060
|
+
app.log.info(
|
|
23061
|
+
{
|
|
23062
|
+
projectId,
|
|
23063
|
+
projectName: project.name,
|
|
23064
|
+
reason,
|
|
23065
|
+
mode: "preview",
|
|
23066
|
+
runs: detection.scan.runs,
|
|
23067
|
+
snapshots: detection.scan.snapshots,
|
|
23068
|
+
wouldAdd: wouldAdd.slice(0, 20),
|
|
23069
|
+
wouldAddCount: wouldAdd.length,
|
|
23070
|
+
wouldRemove: wouldRemove.slice(0, 20),
|
|
23071
|
+
wouldRemoveCount: wouldRemove.length
|
|
23072
|
+
},
|
|
23073
|
+
"competitor auto aliases (preview): stored nothing; set the project to apply, or run detect --apply, to store them"
|
|
23074
|
+
);
|
|
23075
|
+
});
|
|
23076
|
+
const scheduleCompetitorAutoAliases = (projectId, reason) => {
|
|
23077
|
+
competitorAutoAliasReasons.set(projectId, [...competitorAutoAliasReasons.get(projectId) ?? [], reason]);
|
|
23078
|
+
competitorAutoAliasPasses.request(projectId).catch((err) => {
|
|
23079
|
+
app.log.error({ err, projectId, reason }, "competitor auto alias pass failed");
|
|
23080
|
+
});
|
|
23081
|
+
};
|
|
23082
|
+
runCoordinator.onAnswersRecorded = (runId, projectId) => {
|
|
23083
|
+
scheduleCompetitorAutoAliases(projectId, `run:${runId}`);
|
|
23084
|
+
};
|
|
23085
|
+
app.addHook("onClose", async () => {
|
|
23086
|
+
await competitorAutoAliasPasses.settled();
|
|
23087
|
+
await competitorAutoAliases.settled();
|
|
23088
|
+
await answerFieldRecomputes.settled();
|
|
23089
|
+
});
|
|
23090
|
+
app.addHook("onReady", async () => {
|
|
23091
|
+
resumeAnswerFieldRecomputes();
|
|
23092
|
+
});
|
|
22610
23093
|
app.addHook("onReady", async () => {
|
|
22611
23094
|
sentimentTimer = setInterval(pollSentiment, 5e3);
|
|
22612
23095
|
sentimentTimer.unref();
|
|
@@ -23168,37 +23651,14 @@ async function createServer(opts) {
|
|
|
23168
23651
|
}
|
|
23169
23652
|
},
|
|
23170
23653
|
onAliasesChanged: (projectId, projectName) => {
|
|
23171
|
-
|
|
23172
|
-
try {
|
|
23173
|
-
const result2 = backfillProjectAnswerMentions(opts.db, projectId);
|
|
23174
|
-
app.log.info(
|
|
23175
|
-
{ projectId, projectName, ...result2 },
|
|
23176
|
-
"aliases changed \u2014 recomputed mention fields on historical snapshots"
|
|
23177
|
-
);
|
|
23178
|
-
} catch (err) {
|
|
23179
|
-
app.log.error(
|
|
23180
|
-
{ err, projectId, projectName },
|
|
23181
|
-
"alias-triggered backfill failed"
|
|
23182
|
-
);
|
|
23183
|
-
}
|
|
23184
|
-
});
|
|
23654
|
+
requestAnswerFieldRecompute(projectId, projectName, { full: true }, "project-aliases");
|
|
23185
23655
|
},
|
|
23186
|
-
onCompetitorAliasesChanged:
|
|
23187
|
-
|
|
23188
|
-
|
|
23189
|
-
const result2 = backfillProjectAnswerMentions(opts.db, projectId, { competitorFieldsOnly: true });
|
|
23190
|
-
app.log.info(
|
|
23191
|
-
{ projectId, projectName, ...result2 },
|
|
23192
|
-
"competitor aliases changed: recomputed competitor fields on historical snapshots"
|
|
23193
|
-
);
|
|
23194
|
-
} catch (err) {
|
|
23195
|
-
app.log.error(
|
|
23196
|
-
{ err, projectId, projectName },
|
|
23197
|
-
"competitor-alias-triggered backfill failed"
|
|
23198
|
-
);
|
|
23199
|
-
}
|
|
23200
|
-
});
|
|
23656
|
+
onCompetitorAliasesChanged: recomputeCompetitorFields,
|
|
23657
|
+
onCompetitorAutoAliasRescan: (projectId) => {
|
|
23658
|
+
scheduleCompetitorAutoAliases(projectId, "rescan");
|
|
23201
23659
|
},
|
|
23660
|
+
competitorAnswerAnchors: extractStoredAnswerAnchors,
|
|
23661
|
+
runCompetitorAutoAliasPass: (projectId) => competitorAutoAliases.request(projectId, "api"),
|
|
23202
23662
|
operatorApiKeyIds,
|
|
23203
23663
|
listOperationalLogs: (query) => operationalLogs.list(query),
|
|
23204
23664
|
getTelemetryStatus,
|
|
@@ -23575,6 +24035,7 @@ export {
|
|
|
23575
24035
|
reparseStoredResult as reparseStoredResult3,
|
|
23576
24036
|
reparseStoredResult4,
|
|
23577
24037
|
reparseStoredResult5,
|
|
24038
|
+
resolveStoredApiResponse,
|
|
23578
24039
|
getCloudflareTrafficConnectionBySourceId,
|
|
23579
24040
|
setGoogleAuthConfig,
|
|
23580
24041
|
formatAuditFactorScore,
|