@canonry/canonry 7.10.0 → 7.12.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-BgcvPo6g.js → AuditHistoryPanel-Bpkuj-iF.js} +1 -1
- package/assets/assets/{BacklinksPage-DZ4tauAs.js → BacklinksPage-CbRIZhu5.js} +1 -1
- package/assets/assets/{HistoryPage-DWG52dz1.js → HistoryPage-Dfq1vK9k.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-DTx2iQLV.js → MeasurementPropertyPage-DcH0zYNN.js} +1 -1
- package/assets/assets/ProjectPage-enF4twWt.js +8 -0
- package/assets/assets/{RunsPage-B6MhRqxN.js → RunsPage-aOeJ60pX.js} +1 -1
- package/assets/assets/{SettingsPage-Bxv7OSHT.js → SettingsPage-nA0_ImoQ.js} +1 -1
- package/assets/assets/{SiteHealthSection-C5x7oiFL.js → SiteHealthSection-CUOt7jeo.js} +3 -3
- package/assets/assets/{TrafficPage-C5gyQopi.js → TrafficPage-C0fVYizx.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-Dv2ZRNvN.js → TrafficSourceDetailPage-LMhXbFlA.js} +1 -1
- package/assets/assets/{extract-error-message-C5pr51ph.js → extract-error-message-5aHy0fsK.js} +1 -1
- package/assets/assets/index-BJh8cX5E.js +86 -0
- package/assets/assets/index-BNeiohKS.css +1 -0
- package/assets/assets/{react-sigma_core.esm.min-P5bVaYly.js → react-sigma_core.esm.min-ldX35rMq.js} +1 -1
- package/assets/assets/{v2-overview-adapter-QtI4xO_4.js → v2-overview-adapter-CaO9vY-l.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-5ZBN3EEK.js → chunk-7I64KEGM.js} +2 -2
- package/dist/{chunk-66DVP4DN.js → chunk-AU7OUDWR.js} +5786 -4450
- package/dist/{chunk-VSZJVBVR.js → chunk-BTHEFJCI.js} +60 -8
- package/dist/{chunk-FVMWUT2R.js → chunk-DXI2FTSW.js} +706 -257
- package/dist/{chunk-HL3C6Z7T.js → chunk-EPSHIH63.js} +3 -3
- package/dist/{chunk-52PHEGHT.js → chunk-GQMSJEMZ.js} +3515 -2266
- package/dist/{chunk-IKXVLT4M.js → chunk-SM6ZZAPK.js} +93 -5
- package/dist/cli.js +177 -40
- package/dist/{demo-server-WZIB4JKJ.js → demo-server-WJUA4EIG.js} +4 -4
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-KC62A4X5.js → intelligence-service-DJRH4Z3D.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +12 -12
- package/assets/assets/ProjectPage-CCf70zbI.js +0 -8
- package/assets/assets/index-BhXHm5nG.js +0 -86
- 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-SM6ZZAPK.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-7I64KEGM.js";
|
|
31
31
|
import {
|
|
32
32
|
CanonryMcpToolNames,
|
|
33
33
|
CliError,
|
|
34
34
|
PACKAGE_VERSION,
|
|
35
35
|
canonryMcpTools
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-BTHEFJCI.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,15 +78,19 @@ import {
|
|
|
74
78
|
ccReleasePaths,
|
|
75
79
|
ccReleaseSyncs,
|
|
76
80
|
classifyReviewsError,
|
|
81
|
+
competitorIdentityColumns,
|
|
77
82
|
competitors,
|
|
78
83
|
computeCitedCompetitorDomains,
|
|
84
|
+
cookieIsSecure,
|
|
79
85
|
countAttributes,
|
|
80
86
|
countPopulatedGroups,
|
|
81
87
|
createCredentialChecker,
|
|
82
88
|
createFastifyLogger,
|
|
83
89
|
createLogger,
|
|
90
|
+
createProjectPassQueue,
|
|
84
91
|
createRunCompetitorResolver,
|
|
85
92
|
createUserSession,
|
|
93
|
+
dashboardSessions,
|
|
86
94
|
deliverWebhook,
|
|
87
95
|
determineCitationState,
|
|
88
96
|
discoverySessions,
|
|
@@ -129,6 +137,7 @@ import {
|
|
|
129
137
|
hashAttributes,
|
|
130
138
|
hashLodging,
|
|
131
139
|
hashPlaceDetails,
|
|
140
|
+
hashSessionToken,
|
|
132
141
|
insightNotifyState,
|
|
133
142
|
insights,
|
|
134
143
|
inspectUrl,
|
|
@@ -156,6 +165,7 @@ import {
|
|
|
156
165
|
notifications,
|
|
157
166
|
parseCookieHeader,
|
|
158
167
|
parseJsonColumn,
|
|
168
|
+
previewCompetitorAutoAliases,
|
|
159
169
|
probeLatestRelease,
|
|
160
170
|
projects,
|
|
161
171
|
providerBatchRequests,
|
|
@@ -165,7 +175,9 @@ import {
|
|
|
165
175
|
queryBacklinks,
|
|
166
176
|
querySnapshots,
|
|
167
177
|
queueRunIfProjectIdle,
|
|
178
|
+
readAnswerIdentity,
|
|
168
179
|
readInstalledVersion,
|
|
180
|
+
readMarketCompetitorNames,
|
|
169
181
|
readNegativeReviewMaxStars,
|
|
170
182
|
recordSentimentCompletion,
|
|
171
183
|
redactNotificationUrl,
|
|
@@ -205,7 +217,7 @@ import {
|
|
|
205
217
|
siteLivenessState,
|
|
206
218
|
toAlertView,
|
|
207
219
|
usageCounters
|
|
208
|
-
} from "./chunk-
|
|
220
|
+
} from "./chunk-GQMSJEMZ.js";
|
|
209
221
|
import {
|
|
210
222
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
211
223
|
AGENT_NONE,
|
|
@@ -215,12 +227,14 @@ import {
|
|
|
215
227
|
AdsAdGroupBillingEventTypes,
|
|
216
228
|
AdsCampaignBiddingTypes,
|
|
217
229
|
AgentProviderIds,
|
|
230
|
+
AnchoredSpanSources,
|
|
218
231
|
AppError,
|
|
219
232
|
BacklinkSources,
|
|
220
233
|
CANONRY_NPM_PACKAGE_URL,
|
|
221
234
|
CITED_URL_CAPTURE_VERSION,
|
|
222
235
|
CcReleaseSyncStatuses,
|
|
223
236
|
CheckNotificationPolicies,
|
|
237
|
+
DEFAULT_COMPETITOR_AUTO_ALIAS_MODE,
|
|
224
238
|
DEFAULT_VIEWER_RESEARCH_DAILY_RUN_LIMIT,
|
|
225
239
|
DiscoveryCompetitorTypes,
|
|
226
240
|
FEEDBACK_LIMITS,
|
|
@@ -247,6 +261,7 @@ import {
|
|
|
247
261
|
ProviderBatchStatuses,
|
|
248
262
|
ProviderBatchSubmitError,
|
|
249
263
|
ProviderDispatchModes,
|
|
264
|
+
ProviderNames,
|
|
250
265
|
RATIO_UNIT_META_KEY,
|
|
251
266
|
ROUTE_CAPABLE_CITED_URL_PROVIDERS,
|
|
252
267
|
RUN_FILL_PROVIDER_BREAKER,
|
|
@@ -298,6 +313,7 @@ import {
|
|
|
298
313
|
classifyTemplateLinkEdge,
|
|
299
314
|
cloudflareQueueNameSchema,
|
|
300
315
|
compareSemver,
|
|
316
|
+
competitorIdentityAliases,
|
|
301
317
|
competitorLabelFromDomain,
|
|
302
318
|
compileBrandAliases,
|
|
303
319
|
computeBacklinkSummaryMetrics,
|
|
@@ -364,7 +380,6 @@ import {
|
|
|
364
380
|
missingDependency,
|
|
365
381
|
nextScheduleUpdatedAt,
|
|
366
382
|
normalizeAgentSlug,
|
|
367
|
-
normalizeCompetitorAliases,
|
|
368
383
|
normalizeIdTokens,
|
|
369
384
|
normalizeMeasurementExecutionQueryText,
|
|
370
385
|
normalizeProjectDomain,
|
|
@@ -414,7 +429,7 @@ import {
|
|
|
414
429
|
visibilityReportRequestSchema,
|
|
415
430
|
winnabilityClassLabel,
|
|
416
431
|
withRetry
|
|
417
|
-
} from "./chunk-
|
|
432
|
+
} from "./chunk-AU7OUDWR.js";
|
|
418
433
|
|
|
419
434
|
// src/runtime-context.ts
|
|
420
435
|
function cliRuntimeContext(env = process.env, stdio = process) {
|
|
@@ -3545,7 +3560,8 @@ import fs9 from "fs";
|
|
|
3545
3560
|
import path8 from "path";
|
|
3546
3561
|
import { isIP } from "net";
|
|
3547
3562
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3548
|
-
import {
|
|
3563
|
+
import { setImmediate as yieldToEventLoop4 } from "timers/promises";
|
|
3564
|
+
import { and as and23, eq as eq29, isNotNull as isNotNull4, isNull as isNull4, lte as lte2, ne as ne3, or as or3, sql as sql11 } from "drizzle-orm";
|
|
3549
3565
|
|
|
3550
3566
|
// ../integration-typesafe/src/classifier.ts
|
|
3551
3567
|
import { createHash } from "crypto";
|
|
@@ -4519,6 +4535,42 @@ var geminiAdapter = {
|
|
|
4519
4535
|
}
|
|
4520
4536
|
};
|
|
4521
4537
|
|
|
4538
|
+
// ../provider-gemini/src/anchored-spans.ts
|
|
4539
|
+
function isRecord2(value) {
|
|
4540
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4541
|
+
}
|
|
4542
|
+
function chunkSource(chunk) {
|
|
4543
|
+
if (!isRecord2(chunk) || !isRecord2(chunk.web)) return null;
|
|
4544
|
+
const uri = typeof chunk.web.uri === "string" ? chunk.web.uri : "";
|
|
4545
|
+
const uriHost = hostOf(uri);
|
|
4546
|
+
if (uriHost && uriHost !== VERTEX_AI_SEARCH_PROXY_DOMAIN && registrableDomain(uriHost)) return uri;
|
|
4547
|
+
const title = typeof chunk.web.title === "string" ? chunk.web.title.trim() : "";
|
|
4548
|
+
return title && registrableDomain(title) ? title : null;
|
|
4549
|
+
}
|
|
4550
|
+
function extractAnchoredSpans(rawResponse) {
|
|
4551
|
+
const candidates = Array.isArray(rawResponse.candidates) ? rawResponse.candidates : [];
|
|
4552
|
+
const candidate = candidates[0];
|
|
4553
|
+
if (!isRecord2(candidate) || !isRecord2(candidate.groundingMetadata)) return [];
|
|
4554
|
+
const metadata2 = candidate.groundingMetadata;
|
|
4555
|
+
const chunks = Array.isArray(metadata2.groundingChunks) ? metadata2.groundingChunks : [];
|
|
4556
|
+
const spans = [];
|
|
4557
|
+
for (const support of Array.isArray(metadata2.groundingSupports) ? metadata2.groundingSupports : []) {
|
|
4558
|
+
if (!isRecord2(support) || !isRecord2(support.segment)) continue;
|
|
4559
|
+
const text = support.segment.text;
|
|
4560
|
+
if (typeof text !== "string" || !text.trim()) continue;
|
|
4561
|
+
const sources = /* @__PURE__ */ new Set();
|
|
4562
|
+
for (const index of Array.isArray(support.groundingChunkIndices) ? support.groundingChunkIndices : []) {
|
|
4563
|
+
if (typeof index !== "number" || !Number.isInteger(index) || index < 0 || index >= chunks.length) continue;
|
|
4564
|
+
const source = chunkSource(chunks[index]);
|
|
4565
|
+
if (source) sources.add(source);
|
|
4566
|
+
}
|
|
4567
|
+
for (const source of sources) {
|
|
4568
|
+
spans.push({ text, source, kind: "window", via: AnchoredSpanSources["gemini-support"] });
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4571
|
+
return spans;
|
|
4572
|
+
}
|
|
4573
|
+
|
|
4522
4574
|
// ../provider-openai/src/normalize.ts
|
|
4523
4575
|
import OpenAI from "openai";
|
|
4524
4576
|
|
|
@@ -4961,6 +5013,40 @@ var openaiAdapter = {
|
|
|
4961
5013
|
}
|
|
4962
5014
|
};
|
|
4963
5015
|
|
|
5016
|
+
// ../provider-openai/src/anchored-spans.ts
|
|
5017
|
+
function isRecord3(value) {
|
|
5018
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5019
|
+
}
|
|
5020
|
+
function extractAnchoredSpans2(rawResponse) {
|
|
5021
|
+
const spans = [];
|
|
5022
|
+
const output = Array.isArray(rawResponse.output) ? rawResponse.output : [];
|
|
5023
|
+
for (const item of output) {
|
|
5024
|
+
if (!isRecord3(item) || item.type !== "message" || !Array.isArray(item.content)) continue;
|
|
5025
|
+
for (const part of item.content) {
|
|
5026
|
+
if (!isRecord3(part) || part.type !== "output_text" || typeof part.text !== "string") continue;
|
|
5027
|
+
const text = part.text;
|
|
5028
|
+
const citations = (Array.isArray(part.annotations) ? part.annotations : []).flatMap((annotation) => {
|
|
5029
|
+
if (!isRecord3(annotation) || annotation.type !== "url_citation" || typeof annotation.url !== "string") return [];
|
|
5030
|
+
const start = annotation.start_index;
|
|
5031
|
+
const end = annotation.end_index;
|
|
5032
|
+
if (typeof start !== "number" || typeof end !== "number" || !Number.isInteger(start) || !Number.isInteger(end)) return [];
|
|
5033
|
+
if (start < 0 || end < start || end > text.length) return [];
|
|
5034
|
+
return [{ start, end, url: annotation.url }];
|
|
5035
|
+
}).sort((a, b) => a.start - b.start);
|
|
5036
|
+
let previousEnd = 0;
|
|
5037
|
+
for (const citation of citations) {
|
|
5038
|
+
const lineStart = citation.start === 0 ? 0 : text.lastIndexOf("\n", citation.start - 1) + 1;
|
|
5039
|
+
const from = Math.max(lineStart, previousEnd);
|
|
5040
|
+
if (citation.start > from) {
|
|
5041
|
+
spans.push({ text: text.slice(from, citation.start), source: citation.url, kind: "window", via: AnchoredSpanSources["openai-annotation"] });
|
|
5042
|
+
}
|
|
5043
|
+
previousEnd = Math.max(previousEnd, citation.end);
|
|
5044
|
+
}
|
|
5045
|
+
}
|
|
5046
|
+
}
|
|
5047
|
+
return spans;
|
|
5048
|
+
}
|
|
5049
|
+
|
|
4964
5050
|
// ../provider-claude/src/normalize.ts
|
|
4965
5051
|
import Anthropic from "@anthropic-ai/sdk";
|
|
4966
5052
|
|
|
@@ -5597,6 +5683,40 @@ var claudeAdapter = {
|
|
|
5597
5683
|
}
|
|
5598
5684
|
};
|
|
5599
5685
|
|
|
5686
|
+
// ../provider-claude/src/anchored-spans.ts
|
|
5687
|
+
function isRecord4(value) {
|
|
5688
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5689
|
+
}
|
|
5690
|
+
function leadIn(text) {
|
|
5691
|
+
const end = text.trimEnd().length;
|
|
5692
|
+
if (end === 0) return "";
|
|
5693
|
+
return text.slice(text.lastIndexOf("\n", end - 1) + 1);
|
|
5694
|
+
}
|
|
5695
|
+
function extractAnchoredSpans3(rawResponse) {
|
|
5696
|
+
const spans = [];
|
|
5697
|
+
const content = Array.isArray(rawResponse.content) ? rawResponse.content : [];
|
|
5698
|
+
let previousUncited = null;
|
|
5699
|
+
for (const block of content) {
|
|
5700
|
+
if (!isRecord4(block) || block.type !== "text" || typeof block.text !== "string") continue;
|
|
5701
|
+
const urls = /* @__PURE__ */ new Set();
|
|
5702
|
+
for (const citation of Array.isArray(block.citations) ? block.citations : []) {
|
|
5703
|
+
if (isRecord4(citation) && citation.type === "web_search_result_location" && typeof citation.url === "string") {
|
|
5704
|
+
urls.add(citation.url);
|
|
5705
|
+
}
|
|
5706
|
+
}
|
|
5707
|
+
if (urls.size === 0) {
|
|
5708
|
+
previousUncited = block.text;
|
|
5709
|
+
continue;
|
|
5710
|
+
}
|
|
5711
|
+
const window = (previousUncited === null ? "" : leadIn(previousUncited)) + block.text;
|
|
5712
|
+
for (const url of urls) {
|
|
5713
|
+
spans.push({ text: window, source: url, kind: "window", via: AnchoredSpanSources["claude-citation"] });
|
|
5714
|
+
}
|
|
5715
|
+
previousUncited = null;
|
|
5716
|
+
}
|
|
5717
|
+
return spans;
|
|
5718
|
+
}
|
|
5719
|
+
|
|
5600
5720
|
// ../provider-local/src/normalize.ts
|
|
5601
5721
|
import OpenAI3 from "openai";
|
|
5602
5722
|
|
|
@@ -6631,6 +6751,54 @@ function reparseStoredResult4(rawResponse) {
|
|
|
6631
6751
|
if (agent) return parseAgentResponse(agent);
|
|
6632
6752
|
return parseSonarResponse(rawResponse);
|
|
6633
6753
|
}
|
|
6754
|
+
function extractAnchoredSpans4(rawResponse) {
|
|
6755
|
+
const agent = agentResponseOf(rawResponse);
|
|
6756
|
+
let text;
|
|
6757
|
+
let sourceFor;
|
|
6758
|
+
if (agent) {
|
|
6759
|
+
text = extractAgentAnswerText(agent);
|
|
6760
|
+
const byId = /* @__PURE__ */ new Map();
|
|
6761
|
+
for (const item of agentOutput(agent)) {
|
|
6762
|
+
if (item.type !== "search_results" || !Array.isArray(item.results)) continue;
|
|
6763
|
+
for (const result2 of item.results) {
|
|
6764
|
+
if (!isRecord5(result2) || typeof result2.id !== "number" || typeof result2.url !== "string" || !result2.url) continue;
|
|
6765
|
+
if (!byId.has(result2.id)) byId.set(result2.id, result2.url);
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
6768
|
+
sourceFor = (marker) => byId.get(marker);
|
|
6769
|
+
} else {
|
|
6770
|
+
text = extractAnswerText3(rawResponse);
|
|
6771
|
+
const citations = extractCitations(rawResponse);
|
|
6772
|
+
sourceFor = (marker) => citations[marker - 1];
|
|
6773
|
+
}
|
|
6774
|
+
const spans = [];
|
|
6775
|
+
if (!text.includes("[")) return spans;
|
|
6776
|
+
let groupStart = -1;
|
|
6777
|
+
let groupEnd = -1;
|
|
6778
|
+
let previousEnd = 0;
|
|
6779
|
+
let markers = [];
|
|
6780
|
+
const flush = () => {
|
|
6781
|
+
if (markers.length === 0) return;
|
|
6782
|
+
const lineStart = groupStart === 0 ? 0 : text.lastIndexOf("\n", groupStart - 1) + 1;
|
|
6783
|
+
const from = Math.max(lineStart, previousEnd);
|
|
6784
|
+
const window = text.slice(from, groupStart);
|
|
6785
|
+
if (window.trim()) {
|
|
6786
|
+
for (const source of new Set(markers.map(sourceFor).filter((url) => typeof url === "string" && url.length > 0))) {
|
|
6787
|
+
spans.push({ text: window, source, kind: "window", via: AnchoredSpanSources["perplexity-marker"] });
|
|
6788
|
+
}
|
|
6789
|
+
}
|
|
6790
|
+
previousEnd = groupEnd;
|
|
6791
|
+
markers = [];
|
|
6792
|
+
};
|
|
6793
|
+
for (const match of text.matchAll(/\[(\d{1,3})\]/g)) {
|
|
6794
|
+
if (markers.length > 0 && match.index !== groupEnd) flush();
|
|
6795
|
+
if (markers.length === 0) groupStart = match.index;
|
|
6796
|
+
markers.push(Number(match[1]));
|
|
6797
|
+
groupEnd = match.index + match[0].length;
|
|
6798
|
+
}
|
|
6799
|
+
flush();
|
|
6800
|
+
return spans;
|
|
6801
|
+
}
|
|
6634
6802
|
function createClient3(apiKey) {
|
|
6635
6803
|
return new OpenAI4({ apiKey, baseURL: BASE_URL });
|
|
6636
6804
|
}
|
|
@@ -6642,11 +6810,11 @@ function assertUsableResponse(response) {
|
|
|
6642
6810
|
if (status === void 0 || status === "completed") return;
|
|
6643
6811
|
if (status === "incomplete") {
|
|
6644
6812
|
if (extractAgentAnswerText(response).trim().length > 0) return;
|
|
6645
|
-
const details =
|
|
6813
|
+
const details = isRecord5(response.incomplete_details) ? response.incomplete_details : void 0;
|
|
6646
6814
|
const reason = typeof details?.reason === "string" && details.reason.length > 0 ? details.reason : "no reason given";
|
|
6647
6815
|
throw new Error(`agent response incomplete with no answer: ${reason}`);
|
|
6648
6816
|
}
|
|
6649
|
-
const error =
|
|
6817
|
+
const error = isRecord5(response.error) ? response.error : void 0;
|
|
6650
6818
|
const message = typeof error?.message === "string" && error.message.length > 0 ? error.message : "no error detail";
|
|
6651
6819
|
const type = typeof error?.type === "string" && error.type.length > 0 ? ` (${error.type})` : "";
|
|
6652
6820
|
throw new Error(`agent response ${typeof status === "string" ? status : describeError(status)}: ${message}${type}`);
|
|
@@ -6657,7 +6825,7 @@ function agentResponseOf(rawResponse) {
|
|
|
6657
6825
|
return nested && Array.isArray(nested.output) ? nested : null;
|
|
6658
6826
|
}
|
|
6659
6827
|
function agentOutput(response) {
|
|
6660
|
-
return Array.isArray(response.output) ? response.output.filter(
|
|
6828
|
+
return Array.isArray(response.output) ? response.output.filter(isRecord5) : [];
|
|
6661
6829
|
}
|
|
6662
6830
|
function parseAgentResponse(response) {
|
|
6663
6831
|
const groundingSources = extractAgentGroundingSources(response);
|
|
@@ -6675,7 +6843,7 @@ function extractAgentAnswerText(response) {
|
|
|
6675
6843
|
for (const item of agentOutput(response)) {
|
|
6676
6844
|
if (item.type !== "message" || !Array.isArray(item.content)) continue;
|
|
6677
6845
|
for (const part of item.content) {
|
|
6678
|
-
if (
|
|
6846
|
+
if (isRecord5(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
|
|
6679
6847
|
}
|
|
6680
6848
|
}
|
|
6681
6849
|
return parts.join("");
|
|
@@ -6684,7 +6852,7 @@ function extractAgentGroundingSources(response) {
|
|
|
6684
6852
|
const sources = [];
|
|
6685
6853
|
const seen = /* @__PURE__ */ new Set();
|
|
6686
6854
|
const add = (entry) => {
|
|
6687
|
-
if (!
|
|
6855
|
+
if (!isRecord5(entry) || typeof entry.url !== "string" || entry.url.length === 0) return;
|
|
6688
6856
|
if (seen.has(entry.url)) return;
|
|
6689
6857
|
seen.add(entry.url);
|
|
6690
6858
|
sources.push({ uri: entry.url, title: typeof entry.title === "string" ? entry.title : "" });
|
|
@@ -6696,7 +6864,7 @@ function extractAgentGroundingSources(response) {
|
|
|
6696
6864
|
item.contents.forEach(add);
|
|
6697
6865
|
} else if (item.type === "message" && Array.isArray(item.content)) {
|
|
6698
6866
|
for (const part of item.content) {
|
|
6699
|
-
if (
|
|
6867
|
+
if (isRecord5(part) && Array.isArray(part.annotations)) part.annotations.forEach(add);
|
|
6700
6868
|
}
|
|
6701
6869
|
}
|
|
6702
6870
|
}
|
|
@@ -6713,13 +6881,13 @@ function extractAgentSearchQueries(response) {
|
|
|
6713
6881
|
return [...queries2];
|
|
6714
6882
|
}
|
|
6715
6883
|
function extractAgentUsage(response) {
|
|
6716
|
-
const usage =
|
|
6884
|
+
const usage = isRecord5(response.usage) ? response.usage : void 0;
|
|
6717
6885
|
if (!usage) return void 0;
|
|
6718
|
-
const details =
|
|
6886
|
+
const details = isRecord5(usage.input_tokens_details) ? usage.input_tokens_details : void 0;
|
|
6719
6887
|
const cachedInputTokens = usageCount(details?.cache_read_input_tokens);
|
|
6720
6888
|
const cacheWriteTokens = usageCount(details?.cache_creation_input_tokens);
|
|
6721
|
-
const toolCalls =
|
|
6722
|
-
const webSearch =
|
|
6889
|
+
const toolCalls = isRecord5(usage.tool_calls_details) ? usage.tool_calls_details : void 0;
|
|
6890
|
+
const webSearch = isRecord5(toolCalls?.web_search) ? toolCalls.web_search : void 0;
|
|
6723
6891
|
const searchCount = typeof webSearch?.invocation === "number" ? usageCount(webSearch.invocation) : agentOutput(response).filter((item) => item.type === "search_results").length;
|
|
6724
6892
|
return {
|
|
6725
6893
|
inputTokens: Math.max(0, usageCount(usage.input_tokens) - cachedInputTokens - cacheWriteTokens),
|
|
@@ -6730,7 +6898,7 @@ function extractAgentUsage(response) {
|
|
|
6730
6898
|
};
|
|
6731
6899
|
}
|
|
6732
6900
|
function extractAgentStopReason(response) {
|
|
6733
|
-
const details =
|
|
6901
|
+
const details = isRecord5(response.incomplete_details) ? response.incomplete_details : void 0;
|
|
6734
6902
|
if (typeof details?.reason === "string" && details.reason.length > 0) return details.reason;
|
|
6735
6903
|
return typeof response.status === "string" && response.status.length > 0 ? response.status : void 0;
|
|
6736
6904
|
}
|
|
@@ -6798,7 +6966,7 @@ function extractSearchResults(rawResponse) {
|
|
|
6798
6966
|
function parseSearchResultsArray(value) {
|
|
6799
6967
|
if (!Array.isArray(value)) return [];
|
|
6800
6968
|
return value.flatMap((result2) => {
|
|
6801
|
-
if (!
|
|
6969
|
+
if (!isRecord5(result2)) return [];
|
|
6802
6970
|
const url = result2.url;
|
|
6803
6971
|
if (typeof url !== "string" || url.length === 0) {
|
|
6804
6972
|
return [];
|
|
@@ -6825,9 +6993,9 @@ function extractAnswerText3(rawResponse) {
|
|
|
6825
6993
|
}
|
|
6826
6994
|
}
|
|
6827
6995
|
function extractNestedApiResponse(rawResponse) {
|
|
6828
|
-
return
|
|
6996
|
+
return isRecord5(rawResponse.apiResponse) ? rawResponse.apiResponse : null;
|
|
6829
6997
|
}
|
|
6830
|
-
function
|
|
6998
|
+
function isRecord5(value) {
|
|
6831
6999
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6832
7000
|
}
|
|
6833
7001
|
function extractCitedDomains2(groundingSources) {
|
|
@@ -7873,7 +8041,7 @@ function nonBlank(value) {
|
|
|
7873
8041
|
return trimmed ? trimmed : void 0;
|
|
7874
8042
|
}
|
|
7875
8043
|
function resolveBuildCommit(env = process.env) {
|
|
7876
|
-
const embedded = true ? "
|
|
8044
|
+
const embedded = true ? "0c7138fae1222b76fc53fbc904a8e30d18ee72bf" : void 0;
|
|
7877
8045
|
return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
|
|
7878
8046
|
}
|
|
7879
8047
|
function resolveInstanceIdentity(env = process.env) {
|
|
@@ -8295,17 +8463,15 @@ function runOutcome(inserted, providerErrors, planShortfall) {
|
|
|
8295
8463
|
error: someFailed ? serializeRunError(buildProviderRunError(providerErrors)) : null
|
|
8296
8464
|
};
|
|
8297
8465
|
}
|
|
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
8466
|
function runRecordingContext(db, input) {
|
|
8302
|
-
const
|
|
8467
|
+
const identity = answerIdentityFrom(input.project, input.competitors, input.marketNames);
|
|
8468
|
+
const resolveCompetitors = createRunCompetitorResolver(db, identity.competitors, identity.marketNames);
|
|
8303
8469
|
return {
|
|
8304
8470
|
runId: input.runId,
|
|
8305
|
-
|
|
8471
|
+
identity: identity.fingerprint,
|
|
8472
|
+
allDomains: identity.projectDomains,
|
|
8306
8473
|
competitorsFor: (executionId) => resolveCompetitors(input.measurementPlanVersionId, executionId),
|
|
8307
|
-
allBrandNames:
|
|
8308
|
-
competitorIdentity: competitorIdentityKey(input.competitors)
|
|
8474
|
+
allBrandNames: identity.projectBrandNames
|
|
8309
8475
|
};
|
|
8310
8476
|
}
|
|
8311
8477
|
function resolvePlanExecution(run, projectQueries) {
|
|
@@ -8574,13 +8740,14 @@ var JobRunner = class {
|
|
|
8574
8740
|
throw new Error("No providers configured. Add at least one provider API key.");
|
|
8575
8741
|
}
|
|
8576
8742
|
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();
|
|
8743
|
+
const projectCompetitors = this.db.select().from(competitors).where(eq3(competitors.projectId, projectId)).orderBy(competitors.domain).all();
|
|
8578
8744
|
const competitorDomains = projectCompetitors.map((c) => c.domain);
|
|
8579
8745
|
recording = runRecordingContext(this.db, {
|
|
8580
8746
|
runId,
|
|
8581
8747
|
measurementPlanVersionId: existingRun.measurementPlanVersionId,
|
|
8582
8748
|
project,
|
|
8583
|
-
competitors: projectCompetitors
|
|
8749
|
+
competitors: projectCompetitors,
|
|
8750
|
+
marketNames: readMarketCompetitorNames(this.db, projectId)
|
|
8584
8751
|
});
|
|
8585
8752
|
const { allDomains, allBrandNames } = recording;
|
|
8586
8753
|
const { aliases: competitorAliases } = recording.competitorsFor(null);
|
|
@@ -8808,14 +8975,17 @@ var JobRunner = class {
|
|
|
8808
8975
|
requestedModel: config.model ?? null
|
|
8809
8976
|
})),
|
|
8810
8977
|
// Freeze the exact identity we actually dispatched with (domain label
|
|
8811
|
-
// plus curated
|
|
8812
|
-
//
|
|
8813
|
-
//
|
|
8814
|
-
//
|
|
8978
|
+
// plus curated and auto-detected aliases, minus blocked names) so
|
|
8979
|
+
// later reporting never borrows renamed or newly added competitors,
|
|
8980
|
+
// or later alias edits or detections, from live project state. A
|
|
8981
|
+
// competitor with no alias of either kind freezes exactly as it did
|
|
8982
|
+
// before aliases existed, so its definition checksum is unchanged;
|
|
8983
|
+
// an auto-detected name moves the checksum only for runs dispatched
|
|
8984
|
+
// after it was applied.
|
|
8815
8985
|
competitors: projectCompetitors.map((competitor) => {
|
|
8816
8986
|
const label = competitorLabelFromDomain(competitor.domain);
|
|
8817
|
-
const
|
|
8818
|
-
return { domain: competitor.domain, label, aliases: [label, ...
|
|
8987
|
+
const named = competitorIdentityAliases(competitor).filter((alias) => alias.toLowerCase() !== label.toLowerCase());
|
|
8988
|
+
return { domain: competitor.domain, label, aliases: [label, ...named] };
|
|
8819
8989
|
}),
|
|
8820
8990
|
queries: projectQueries.map((query) => ({
|
|
8821
8991
|
queryId: query.id,
|
|
@@ -8876,7 +9046,7 @@ var JobRunner = class {
|
|
|
8876
9046
|
}
|
|
8877
9047
|
}
|
|
8878
9048
|
providerCallEnd = Date.now();
|
|
8879
|
-
this.
|
|
9049
|
+
this.reconcileRunAnswerFields(recording, projectId);
|
|
8880
9050
|
this.throwIfRunCancelled(runId);
|
|
8881
9051
|
if (planExecution) {
|
|
8882
9052
|
for (const [provider, units] of planExecution.unitsByProvider) {
|
|
@@ -8914,7 +9084,7 @@ var JobRunner = class {
|
|
|
8914
9084
|
});
|
|
8915
9085
|
if (!finalized && this.isRunCancelled(runId)) throw new RunCancelledError(runId);
|
|
8916
9086
|
} catch (err) {
|
|
8917
|
-
if (recording) this.
|
|
9087
|
+
if (recording) this.reconcileRunAnswerFields(recording, projectId);
|
|
8918
9088
|
const executionContext = {
|
|
8919
9089
|
providerCount: activeProviders.length,
|
|
8920
9090
|
providers: activeProviders.map((provider) => provider.adapter.name),
|
|
@@ -9556,7 +9726,7 @@ var JobRunner = class {
|
|
|
9556
9726
|
this.markBatchesCancelled([batch.id], "Cancelled because its run is no longer running.");
|
|
9557
9727
|
return { kind: "cancelled" };
|
|
9558
9728
|
} finally {
|
|
9559
|
-
this.
|
|
9729
|
+
this.reconcileRunAnswerFields(ctx, batch.projectId);
|
|
9560
9730
|
}
|
|
9561
9731
|
return this.completeBatchIngest(batch);
|
|
9562
9732
|
}
|
|
@@ -9847,7 +10017,7 @@ var JobRunner = class {
|
|
|
9847
10017
|
fatal = describeError(err);
|
|
9848
10018
|
log2.error("fill.failed", { fillId, runId, error: fatal });
|
|
9849
10019
|
} finally {
|
|
9850
|
-
if (recording) this.
|
|
10020
|
+
if (recording) this.reconcileRunAnswerFields(recording, projectId);
|
|
9851
10021
|
this.flushProviderUsage(providerDispatchCounts, providerReservations);
|
|
9852
10022
|
}
|
|
9853
10023
|
const outcome = this.finalizeRunFill({ fillId, runId, filled, fatal, providerErrors, superseded });
|
|
@@ -10097,34 +10267,38 @@ var JobRunner = class {
|
|
|
10097
10267
|
if (!run) throw new Error(`Run ${runId} not found`);
|
|
10098
10268
|
const project = this.db.select().from(projects).where(eq3(projects.id, projectId)).get();
|
|
10099
10269
|
if (!project) throw new Error(`Project ${projectId} not found`);
|
|
10100
|
-
const projectCompetitors = this.db.select().from(competitors).where(eq3(competitors.projectId, projectId)).all();
|
|
10270
|
+
const projectCompetitors = this.db.select().from(competitors).where(eq3(competitors.projectId, projectId)).orderBy(competitors.domain).all();
|
|
10101
10271
|
return runRecordingContext(this.db, {
|
|
10102
10272
|
runId,
|
|
10103
10273
|
measurementPlanVersionId: run.measurementPlanVersionId,
|
|
10104
10274
|
project,
|
|
10105
|
-
competitors: projectCompetitors
|
|
10275
|
+
competitors: projectCompetitors,
|
|
10276
|
+
marketNames: readMarketCompetitorNames(this.db, projectId)
|
|
10106
10277
|
});
|
|
10107
10278
|
}
|
|
10108
10279
|
/**
|
|
10109
|
-
* Rescore a run's stored
|
|
10110
|
-
* changed after `recording` read
|
|
10280
|
+
* Rescore a run's stored answer fields when the identity they are scored
|
|
10281
|
+
* against changed after `recording` read it (`answerIdentityChanged`).
|
|
10111
10282
|
*
|
|
10112
|
-
* Saving a competitor alias
|
|
10113
|
-
*
|
|
10114
|
-
*
|
|
10115
|
-
*
|
|
10116
|
-
*
|
|
10117
|
-
*
|
|
10118
|
-
*
|
|
10283
|
+
* Saving a competitor alias, a detection pass that changes a competitor's
|
|
10284
|
+
* auto-detected names, or a project alias edit rescores the stored answers
|
|
10285
|
+
* once (`onCompetitorAliasesChanged`, `onAliasesChanged`). A writer that read
|
|
10286
|
+
* the identity before the edit keeps scoring with it, so an answer it stores
|
|
10287
|
+
* afterwards would disagree with an identical one the edit rescored. Each
|
|
10288
|
+
* writer calls this after its last write: when its identity is no longer
|
|
10289
|
+
* current, the whole run gets the pass the edit ran, the competitor fields
|
|
10290
|
+
* only, or `answer_mentioned` too when the project's own identity moved. A
|
|
10291
|
+
* failure is logged and never fails the writer.
|
|
10119
10292
|
*/
|
|
10120
|
-
|
|
10293
|
+
reconcileRunAnswerFields(recording, projectId) {
|
|
10121
10294
|
try {
|
|
10122
|
-
const current = this.db
|
|
10123
|
-
if (
|
|
10124
|
-
const
|
|
10125
|
-
|
|
10295
|
+
const current = readAnswerIdentity(this.db, projectId);
|
|
10296
|
+
if (!current || !answerIdentityChanged(recording.identity, current.fingerprint)) return;
|
|
10297
|
+
const full = recording.identity.project !== current.fingerprint.project;
|
|
10298
|
+
const result2 = backfillProjectAnswerMentions(this.db, projectId, { competitorFieldsOnly: !full, runId: recording.runId });
|
|
10299
|
+
log2.info("run.answer-fields-rescored", { runId: recording.runId, projectId, full, ...result2 });
|
|
10126
10300
|
} catch (err) {
|
|
10127
|
-
log2.error("run.
|
|
10301
|
+
log2.error("run.answer-fields-rescore-failed", { runId: recording.runId, projectId, error: describeError(err) });
|
|
10128
10302
|
}
|
|
10129
10303
|
}
|
|
10130
10304
|
incrementUsage(scope, metric, count3) {
|
|
@@ -11142,11 +11316,11 @@ function validateGeoSearchLimit(limit) {
|
|
|
11142
11316
|
);
|
|
11143
11317
|
}
|
|
11144
11318
|
}
|
|
11145
|
-
function
|
|
11319
|
+
function isRecord6(value) {
|
|
11146
11320
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11147
11321
|
}
|
|
11148
11322
|
function validateRequestObject(value, label) {
|
|
11149
|
-
if (!
|
|
11323
|
+
if (!isRecord6(value)) {
|
|
11150
11324
|
throw new OpenAiAdsApiError(`${label} must be a JSON object`, 400);
|
|
11151
11325
|
}
|
|
11152
11326
|
}
|
|
@@ -11305,7 +11479,7 @@ function validateLandingPageConfiguration(value, label) {
|
|
|
11305
11479
|
}
|
|
11306
11480
|
}
|
|
11307
11481
|
function parseLandingPageQueryStringTemplate(value) {
|
|
11308
|
-
if (!
|
|
11482
|
+
if (!isRecord6(value)) return null;
|
|
11309
11483
|
const template = value.query_string_template;
|
|
11310
11484
|
return typeof template === "string" && template.trim().length > 0 ? template : null;
|
|
11311
11485
|
}
|
|
@@ -12899,7 +13073,7 @@ async function executeDiscoveryRun(opts) {
|
|
|
12899
13073
|
try {
|
|
12900
13074
|
const projectRow = opts.db.select().from(projects).where(eq14(projects.id, opts.projectId)).get();
|
|
12901
13075
|
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());
|
|
13076
|
+
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
13077
|
const canonicalDomains = effectiveDomains({
|
|
12904
13078
|
canonicalDomain: projectRow.canonicalDomain,
|
|
12905
13079
|
ownedDomains: projectRow.ownedDomains
|
|
@@ -14242,6 +14416,115 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
14242
14416
|
}
|
|
14243
14417
|
}
|
|
14244
14418
|
|
|
14419
|
+
// src/answer-field-recompute.ts
|
|
14420
|
+
import { setImmediate as yieldToEventLoop2 } from "timers/promises";
|
|
14421
|
+
function createAnswerFieldRecomputeQueue(opts) {
|
|
14422
|
+
const pendingFull = /* @__PURE__ */ new Map();
|
|
14423
|
+
const queue = createProjectPassQueue(async (projectId) => {
|
|
14424
|
+
const full = pendingFull.get(projectId) ?? false;
|
|
14425
|
+
pendingFull.delete(projectId);
|
|
14426
|
+
await yieldToEventLoop2();
|
|
14427
|
+
const result2 = await backfillProjectAnswerMentionsInChunks(opts.db, projectId, { competitorFieldsOnly: !full });
|
|
14428
|
+
return { full, result: result2 };
|
|
14429
|
+
});
|
|
14430
|
+
return {
|
|
14431
|
+
request(projectId, scope) {
|
|
14432
|
+
pendingFull.set(projectId, (pendingFull.get(projectId) ?? false) || scope.full === true);
|
|
14433
|
+
return queue.request(projectId);
|
|
14434
|
+
},
|
|
14435
|
+
settled: () => queue.settled()
|
|
14436
|
+
};
|
|
14437
|
+
}
|
|
14438
|
+
|
|
14439
|
+
// src/competitor-auto-alias-runner.ts
|
|
14440
|
+
import { setImmediate as yieldToEventLoop3 } from "timers/promises";
|
|
14441
|
+
var log15 = createLogger("CompetitorAutoAliases");
|
|
14442
|
+
function createCompetitorAutoAliasRunner(opts) {
|
|
14443
|
+
const reasons = /* @__PURE__ */ new Map();
|
|
14444
|
+
const queue = createProjectPassQueue(async (projectId) => {
|
|
14445
|
+
await yieldToEventLoop3();
|
|
14446
|
+
const reason = (reasons.get(projectId) ?? []).join(",");
|
|
14447
|
+
reasons.delete(projectId);
|
|
14448
|
+
const result2 = await applyCompetitorAutoAliases(opts.db, projectId, { readAnchors: opts.readAnchors });
|
|
14449
|
+
if (!result2) return null;
|
|
14450
|
+
const added = result2.detection.competitors.reduce((sum, competitor) => sum + competitor.added.length, 0);
|
|
14451
|
+
const removed = result2.detection.competitors.reduce((sum, competitor) => sum + competitor.removed.length, 0);
|
|
14452
|
+
log15.info("detection.completed", {
|
|
14453
|
+
projectId,
|
|
14454
|
+
reason,
|
|
14455
|
+
runs: result2.detection.scan.runs,
|
|
14456
|
+
snapshots: result2.detection.scan.snapshots,
|
|
14457
|
+
added,
|
|
14458
|
+
removed,
|
|
14459
|
+
namesChanged: result2.namesChanged
|
|
14460
|
+
});
|
|
14461
|
+
if (result2.namesChanged) {
|
|
14462
|
+
try {
|
|
14463
|
+
opts.onNamesChanged(projectId, result2.detection.project);
|
|
14464
|
+
} catch (err) {
|
|
14465
|
+
log15.error("names-changed.failed", { projectId, error: describeError(err) });
|
|
14466
|
+
}
|
|
14467
|
+
}
|
|
14468
|
+
return result2;
|
|
14469
|
+
});
|
|
14470
|
+
const enqueue = (projectId, reason) => {
|
|
14471
|
+
reasons.set(projectId, [...reasons.get(projectId) ?? [], reason]);
|
|
14472
|
+
return queue.request(projectId);
|
|
14473
|
+
};
|
|
14474
|
+
return {
|
|
14475
|
+
schedule(projectId, reason) {
|
|
14476
|
+
enqueue(projectId, reason).catch((err) => {
|
|
14477
|
+
log15.error("detection.failed", { projectId, reason, error: describeError(err) });
|
|
14478
|
+
});
|
|
14479
|
+
},
|
|
14480
|
+
async request(projectId, reason) {
|
|
14481
|
+
try {
|
|
14482
|
+
return await enqueue(projectId, reason);
|
|
14483
|
+
} catch (err) {
|
|
14484
|
+
log15.error("detection.failed", { projectId, reason, error: describeError(err) });
|
|
14485
|
+
throw err;
|
|
14486
|
+
}
|
|
14487
|
+
},
|
|
14488
|
+
settled: () => queue.settled()
|
|
14489
|
+
};
|
|
14490
|
+
}
|
|
14491
|
+
|
|
14492
|
+
// src/stored-answer-anchors.ts
|
|
14493
|
+
function resolveStoredApiResponse(parsed) {
|
|
14494
|
+
const nested = parsed.apiResponse;
|
|
14495
|
+
if (nested !== null && typeof nested === "object" && !Array.isArray(nested)) {
|
|
14496
|
+
return nested;
|
|
14497
|
+
}
|
|
14498
|
+
if (looksLikeProviderApiResponse(parsed)) {
|
|
14499
|
+
return parsed;
|
|
14500
|
+
}
|
|
14501
|
+
return null;
|
|
14502
|
+
}
|
|
14503
|
+
function looksLikeProviderApiResponse(value) {
|
|
14504
|
+
return Array.isArray(value.output) || Array.isArray(value.content) || Array.isArray(value.candidates) || Array.isArray(value.choices);
|
|
14505
|
+
}
|
|
14506
|
+
function extractStoredAnswerAnchors(provider, rawResponse) {
|
|
14507
|
+
if (!rawResponse) return [];
|
|
14508
|
+
const apiResponse = resolveStoredApiResponse(parseJsonColumn(rawResponse, {}));
|
|
14509
|
+
if (!apiResponse) return [];
|
|
14510
|
+
try {
|
|
14511
|
+
switch (provider) {
|
|
14512
|
+
case ProviderNames.openai:
|
|
14513
|
+
return extractAnchoredSpans2(apiResponse);
|
|
14514
|
+
case ProviderNames.claude:
|
|
14515
|
+
return extractAnchoredSpans3(apiResponse);
|
|
14516
|
+
case ProviderNames.gemini:
|
|
14517
|
+
return extractAnchoredSpans(apiResponse);
|
|
14518
|
+
case ProviderNames.perplexity:
|
|
14519
|
+
return extractAnchoredSpans4(apiResponse);
|
|
14520
|
+
default:
|
|
14521
|
+
return [];
|
|
14522
|
+
}
|
|
14523
|
+
} catch {
|
|
14524
|
+
return [];
|
|
14525
|
+
}
|
|
14526
|
+
}
|
|
14527
|
+
|
|
14245
14528
|
// src/provider-registry.ts
|
|
14246
14529
|
var ProviderRegistry = class {
|
|
14247
14530
|
providers = /* @__PURE__ */ new Map();
|
|
@@ -14358,7 +14641,7 @@ function createProviderModelCatalog(registry) {
|
|
|
14358
14641
|
import crypto21 from "crypto";
|
|
14359
14642
|
import cron from "node-cron";
|
|
14360
14643
|
import { and as and15, eq as eq17, inArray as inArray7, notExists, sql as sql7 } from "drizzle-orm";
|
|
14361
|
-
var
|
|
14644
|
+
var log16 = createLogger("Scheduler");
|
|
14362
14645
|
var DEFAULT_HEALTH_CRON = "0 */6 * * *";
|
|
14363
14646
|
function ensureDefaultHealthSchedule(db, projectId, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
14364
14647
|
const result2 = db.insert(schedules).values({
|
|
@@ -14420,7 +14703,7 @@ var Scheduler = class {
|
|
|
14420
14703
|
if (ensureDefaultHealthSchedule(this.db, project.id, now)) seeded += 1;
|
|
14421
14704
|
}
|
|
14422
14705
|
if (seeded > 0) {
|
|
14423
|
-
|
|
14706
|
+
log16.info("health-schedule.seeded", { projectCount: seeded, cron: DEFAULT_HEALTH_CRON });
|
|
14424
14707
|
}
|
|
14425
14708
|
}
|
|
14426
14709
|
/**
|
|
@@ -14444,10 +14727,10 @@ var Scheduler = class {
|
|
|
14444
14727
|
const current = ensureCurrentQueryBasketRevision(this.db, project.id);
|
|
14445
14728
|
if (current !== null && current.revision !== previous) minted += 1;
|
|
14446
14729
|
} catch (err) {
|
|
14447
|
-
|
|
14730
|
+
log16.warn("query-basket.mint-failed", { projectId: project.id, err: String(err) });
|
|
14448
14731
|
}
|
|
14449
14732
|
}
|
|
14450
|
-
if (minted > 0)
|
|
14733
|
+
if (minted > 0) log16.info("query-basket.ensured", { projectCount: minted });
|
|
14451
14734
|
}
|
|
14452
14735
|
/** Load all enabled schedules from DB and register cron jobs. */
|
|
14453
14736
|
start() {
|
|
@@ -14461,12 +14744,12 @@ var Scheduler = class {
|
|
|
14461
14744
|
if (registered && missedRunAt && new Date(missedRunAt) < /* @__PURE__ */ new Date()) {
|
|
14462
14745
|
const answerRecurrence = recurrence && schedule.kind === SchedulableRunKinds["answer-visibility"];
|
|
14463
14746
|
if (!recurrence || answerRecurrence || this.claimCalendarOccurrence(schedule, missedRunAt, /* @__PURE__ */ new Date())) {
|
|
14464
|
-
|
|
14747
|
+
log16.info("run.catch-up", { projectId: schedule.projectId, kind: schedule.kind, missedRunAt });
|
|
14465
14748
|
this.triggerRun(schedule.id, schedule.projectId, schedule.kind, answerRecurrence ? missedRunAt : void 0);
|
|
14466
14749
|
}
|
|
14467
14750
|
}
|
|
14468
14751
|
}
|
|
14469
|
-
|
|
14752
|
+
log16.info("started", { scheduleCount: allSchedules.length });
|
|
14470
14753
|
}
|
|
14471
14754
|
/** Stop all cron tasks for graceful shutdown. */
|
|
14472
14755
|
stop() {
|
|
@@ -14510,7 +14793,7 @@ var Scheduler = class {
|
|
|
14510
14793
|
stopTask(key, task, verb) {
|
|
14511
14794
|
void task.stop();
|
|
14512
14795
|
void task.destroy();
|
|
14513
|
-
|
|
14796
|
+
log16.info(`task.${verb.toLowerCase()}`, { key });
|
|
14514
14797
|
}
|
|
14515
14798
|
/** Runtime timing writes must not restore a version already issued to a settings caller. */
|
|
14516
14799
|
updateScheduleTiming(scheduleId, timing) {
|
|
@@ -14530,13 +14813,13 @@ var Scheduler = class {
|
|
|
14530
14813
|
return true;
|
|
14531
14814
|
}
|
|
14532
14815
|
if (schedule.recurrence) {
|
|
14533
|
-
|
|
14816
|
+
log16.error("calendar.invalid", { projectId: schedule.projectId, kind: schedule.kind });
|
|
14534
14817
|
return false;
|
|
14535
14818
|
}
|
|
14536
14819
|
const { id: scheduleId, projectId, cronExpr, timezone } = schedule;
|
|
14537
14820
|
const kind = schedule.kind;
|
|
14538
14821
|
if (!cron.validate(cronExpr)) {
|
|
14539
|
-
|
|
14822
|
+
log16.error("cron.invalid", { projectId, kind, cronExpr });
|
|
14540
14823
|
return false;
|
|
14541
14824
|
}
|
|
14542
14825
|
const task = cron.schedule(cronExpr, () => {
|
|
@@ -14551,7 +14834,7 @@ var Scheduler = class {
|
|
|
14551
14834
|
});
|
|
14552
14835
|
}
|
|
14553
14836
|
const label = schedule.preset ?? cronExpr;
|
|
14554
|
-
|
|
14837
|
+
log16.info("cron.registered", { projectId, kind, schedule: label, timezone });
|
|
14555
14838
|
return true;
|
|
14556
14839
|
}
|
|
14557
14840
|
/** Calendar schedules are checked at most once a minute so long intervals
|
|
@@ -14609,7 +14892,7 @@ var Scheduler = class {
|
|
|
14609
14892
|
}
|
|
14610
14893
|
if (options.preserveNextRunAt) arm(registeredNextRunAt);
|
|
14611
14894
|
else timer = setTimeout(tick, 0);
|
|
14612
|
-
|
|
14895
|
+
log16.info("calendar.registered", { projectId, kind, recurrence, timezone });
|
|
14613
14896
|
}
|
|
14614
14897
|
/** Atomically advance a due callback-only calendar occurrence before dispatch. */
|
|
14615
14898
|
claimCalendarOccurrence(schedule, dueAt, now) {
|
|
@@ -14637,13 +14920,13 @@ var Scheduler = class {
|
|
|
14637
14920
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14638
14921
|
const currentSchedule = this.db.select().from(schedules).where(eq17(schedules.id, scheduleId)).get();
|
|
14639
14922
|
if (!currentSchedule || !currentSchedule.enabled) {
|
|
14640
|
-
|
|
14923
|
+
log16.warn("schedule.stale", { scheduleId, projectId, kind, msg: "schedule no longer exists or is disabled" });
|
|
14641
14924
|
this.remove(projectId, kind);
|
|
14642
14925
|
return;
|
|
14643
14926
|
}
|
|
14644
14927
|
const recurrence = scheduleRecurrence(currentSchedule);
|
|
14645
14928
|
if (claimedOccurrence && (!recurrence || currentSchedule.nextRunAt !== claimedOccurrence)) {
|
|
14646
|
-
|
|
14929
|
+
log16.info("calendar.stale-claim", { projectId, scheduleId, kind });
|
|
14647
14930
|
return;
|
|
14648
14931
|
}
|
|
14649
14932
|
const nextRunAt = nextRunFromSchedule({
|
|
@@ -14653,31 +14936,31 @@ var Scheduler = class {
|
|
|
14653
14936
|
});
|
|
14654
14937
|
const project = this.db.select().from(projects).where(eq17(projects.id, projectId)).get();
|
|
14655
14938
|
if (!project) {
|
|
14656
|
-
|
|
14939
|
+
log16.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
|
|
14657
14940
|
this.remove(projectId, kind);
|
|
14658
14941
|
return;
|
|
14659
14942
|
}
|
|
14660
14943
|
if (kind === SchedulableRunKinds["traffic-sync"]) {
|
|
14661
14944
|
const sourceId = currentSchedule.sourceId;
|
|
14662
14945
|
if (!sourceId) {
|
|
14663
|
-
|
|
14946
|
+
log16.warn("traffic-sync.missing-source", { scheduleId, projectId });
|
|
14664
14947
|
return;
|
|
14665
14948
|
}
|
|
14666
14949
|
if (!this.callbacks.onTrafficSyncRequested) {
|
|
14667
|
-
|
|
14950
|
+
log16.warn("traffic-sync.no-callback", { scheduleId, projectId, msg: "host did not register onTrafficSyncRequested" });
|
|
14668
14951
|
return;
|
|
14669
14952
|
}
|
|
14670
14953
|
this.updateScheduleTiming(currentSchedule.id, {
|
|
14671
14954
|
lastRunAt: now,
|
|
14672
14955
|
nextRunAt
|
|
14673
14956
|
});
|
|
14674
|
-
|
|
14957
|
+
log16.info("traffic-sync.triggered", { projectName: project.name, sourceId });
|
|
14675
14958
|
this.callbacks.onTrafficSyncRequested(project.name, sourceId);
|
|
14676
14959
|
return;
|
|
14677
14960
|
}
|
|
14678
14961
|
if (kind === SchedulableRunKinds["gbp-sync"]) {
|
|
14679
14962
|
if (!this.callbacks.onGbpSyncRequested) {
|
|
14680
|
-
|
|
14963
|
+
log16.warn("gbp-sync.no-callback", { scheduleId, projectId, msg: "host did not register onGbpSyncRequested" });
|
|
14681
14964
|
return;
|
|
14682
14965
|
}
|
|
14683
14966
|
const runId2 = crypto21.randomUUID();
|
|
@@ -14693,13 +14976,13 @@ var Scheduler = class {
|
|
|
14693
14976
|
lastRunAt: now,
|
|
14694
14977
|
nextRunAt
|
|
14695
14978
|
});
|
|
14696
|
-
|
|
14979
|
+
log16.info("gbp-sync.triggered", { runId: runId2, projectName: project.name });
|
|
14697
14980
|
this.callbacks.onGbpSyncRequested(runId2, projectId);
|
|
14698
14981
|
return;
|
|
14699
14982
|
}
|
|
14700
14983
|
if (kind === SchedulableRunKinds["ads-sync"]) {
|
|
14701
14984
|
if (!this.callbacks.onAdsSyncRequested) {
|
|
14702
|
-
|
|
14985
|
+
log16.warn("ads-sync.no-callback", { scheduleId, projectId, msg: "host did not register onAdsSyncRequested" });
|
|
14703
14986
|
return;
|
|
14704
14987
|
}
|
|
14705
14988
|
const activeAdsRun = this.db.select({ id: runs.id }).from(runs).where(and15(
|
|
@@ -14708,7 +14991,7 @@ var Scheduler = class {
|
|
|
14708
14991
|
inArray7(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
14709
14992
|
)).get();
|
|
14710
14993
|
if (activeAdsRun) {
|
|
14711
|
-
|
|
14994
|
+
log16.info("ads-sync.skipped-active", { projectName: project.name, activeRunId: activeAdsRun.id });
|
|
14712
14995
|
this.updateScheduleTiming(currentSchedule.id, { nextRunAt });
|
|
14713
14996
|
return;
|
|
14714
14997
|
}
|
|
@@ -14725,52 +15008,52 @@ var Scheduler = class {
|
|
|
14725
15008
|
lastRunAt: now,
|
|
14726
15009
|
nextRunAt
|
|
14727
15010
|
});
|
|
14728
|
-
|
|
15011
|
+
log16.info("ads-sync.triggered", { runId: runId2, projectName: project.name });
|
|
14729
15012
|
this.callbacks.onAdsSyncRequested(runId2, projectId);
|
|
14730
15013
|
return;
|
|
14731
15014
|
}
|
|
14732
15015
|
if (kind === SchedulableRunKinds["data-refresh"]) {
|
|
14733
15016
|
if (!this.callbacks.onDataRefreshRequested) {
|
|
14734
|
-
|
|
15017
|
+
log16.warn("data-refresh.no-callback", { scheduleId, projectId, msg: "host did not register onDataRefreshRequested" });
|
|
14735
15018
|
return;
|
|
14736
15019
|
}
|
|
14737
15020
|
this.updateScheduleTiming(currentSchedule.id, {
|
|
14738
15021
|
lastRunAt: now,
|
|
14739
15022
|
nextRunAt
|
|
14740
15023
|
});
|
|
14741
|
-
|
|
15024
|
+
log16.info("data-refresh.triggered", { projectName: project.name });
|
|
14742
15025
|
this.callbacks.onDataRefreshRequested(project.name);
|
|
14743
15026
|
return;
|
|
14744
15027
|
}
|
|
14745
15028
|
if (kind === SchedulableRunKinds.doctor) {
|
|
14746
15029
|
if (!this.callbacks.onDoctorRequested) {
|
|
14747
|
-
|
|
15030
|
+
log16.warn("doctor.no-callback", { scheduleId, projectId, msg: "host did not register onDoctorRequested" });
|
|
14748
15031
|
return;
|
|
14749
15032
|
}
|
|
14750
15033
|
this.updateScheduleTiming(currentSchedule.id, {
|
|
14751
15034
|
lastRunAt: now,
|
|
14752
15035
|
nextRunAt
|
|
14753
15036
|
});
|
|
14754
|
-
|
|
15037
|
+
log16.info("doctor.triggered", { projectName: project.name });
|
|
14755
15038
|
this.callbacks.onDoctorRequested(project.name);
|
|
14756
15039
|
return;
|
|
14757
15040
|
}
|
|
14758
15041
|
if (kind === SchedulableRunKinds["backlinks-sync"]) {
|
|
14759
15042
|
if (!this.callbacks.onBacklinksSyncRequested) {
|
|
14760
|
-
|
|
15043
|
+
log16.warn("backlinks-sync.no-callback", { scheduleId, projectId, msg: "host did not register onBacklinksSyncRequested" });
|
|
14761
15044
|
return;
|
|
14762
15045
|
}
|
|
14763
15046
|
this.updateScheduleTiming(currentSchedule.id, {
|
|
14764
15047
|
lastRunAt: now,
|
|
14765
15048
|
nextRunAt
|
|
14766
15049
|
});
|
|
14767
|
-
|
|
15050
|
+
log16.info("backlinks-sync.triggered", { projectName: project.name });
|
|
14768
15051
|
this.callbacks.onBacklinksSyncRequested(project.name);
|
|
14769
15052
|
return;
|
|
14770
15053
|
}
|
|
14771
15054
|
if (kind === SchedulableRunKinds["site-audit"]) {
|
|
14772
15055
|
if (!this.callbacks.onSiteAuditRequested) {
|
|
14773
|
-
|
|
15056
|
+
log16.warn("site-audit.no-callback", { scheduleId, projectId, msg: "host did not register onSiteAuditRequested" });
|
|
14774
15057
|
return;
|
|
14775
15058
|
}
|
|
14776
15059
|
const active = this.db.select({ id: runs.id }).from(runs).where(and15(
|
|
@@ -14779,7 +15062,7 @@ var Scheduler = class {
|
|
|
14779
15062
|
inArray7(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
14780
15063
|
)).get();
|
|
14781
15064
|
if (active) {
|
|
14782
|
-
|
|
15065
|
+
log16.info("site-audit.skipped-active", { projectName: project.name, activeRunId: active.id });
|
|
14783
15066
|
this.updateScheduleTiming(currentSchedule.id, { nextRunAt });
|
|
14784
15067
|
return;
|
|
14785
15068
|
}
|
|
@@ -14806,7 +15089,7 @@ var Scheduler = class {
|
|
|
14806
15089
|
lastRunAt: now,
|
|
14807
15090
|
nextRunAt
|
|
14808
15091
|
});
|
|
14809
|
-
|
|
15092
|
+
log16.info("site-audit.triggered", { runId: runId2, projectName: project.name });
|
|
14810
15093
|
this.callbacks.onSiteAuditRequested(runId2, projectId, { maxPages: effectiveRequest.maxPages });
|
|
14811
15094
|
return;
|
|
14812
15095
|
}
|
|
@@ -14815,7 +15098,7 @@ var Scheduler = class {
|
|
|
14815
15098
|
if (project.defaultLocation) {
|
|
14816
15099
|
const loc = projectLocations.find((l) => l.label === project.defaultLocation);
|
|
14817
15100
|
if (!loc) {
|
|
14818
|
-
|
|
15101
|
+
log16.warn("default-location.stale", { scheduleId, projectId, label: project.defaultLocation });
|
|
14819
15102
|
return;
|
|
14820
15103
|
}
|
|
14821
15104
|
resolvedLocation = loc;
|
|
@@ -14824,7 +15107,7 @@ var Scheduler = class {
|
|
|
14824
15107
|
const scheduleProviders = currentSchedule.providers;
|
|
14825
15108
|
const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
|
|
14826
15109
|
if (claimedOccurrence && recurrence && !nextRunAt) {
|
|
14827
|
-
|
|
15110
|
+
log16.error("calendar.invalid", { projectId, kind, scheduleId });
|
|
14828
15111
|
return;
|
|
14829
15112
|
}
|
|
14830
15113
|
const queueResult = queueRunIfProjectIdle(this.db, {
|
|
@@ -14848,11 +15131,11 @@ var Scheduler = class {
|
|
|
14848
15131
|
});
|
|
14849
15132
|
if (queueResult.conflict) {
|
|
14850
15133
|
if (queueResult.scheduleClaimed === false) {
|
|
14851
|
-
|
|
15134
|
+
log16.info("calendar.skipped-claimed", { projectName: project.name, scheduleId: currentSchedule.id });
|
|
14852
15135
|
return;
|
|
14853
15136
|
}
|
|
14854
15137
|
const batchPending = hasOutstandingProviderBatch(this.db, queueResult.activeRunId);
|
|
14855
|
-
|
|
15138
|
+
log16.info("run.skipped-active", {
|
|
14856
15139
|
projectName: project.name,
|
|
14857
15140
|
activeRunId: queueResult.activeRunId,
|
|
14858
15141
|
...batchPending ? { reason: "batch-pending" } : {}
|
|
@@ -14861,7 +15144,7 @@ var Scheduler = class {
|
|
|
14861
15144
|
return;
|
|
14862
15145
|
}
|
|
14863
15146
|
if (queueResult.refused) {
|
|
14864
|
-
|
|
15147
|
+
log16.warn("run.skipped-providers-failing", {
|
|
14865
15148
|
projectName: project.name,
|
|
14866
15149
|
scheduleId: currentSchedule.id,
|
|
14867
15150
|
...queueResult.refused
|
|
@@ -14872,7 +15155,7 @@ var Scheduler = class {
|
|
|
14872
15155
|
const runId = queueResult.runId;
|
|
14873
15156
|
for (const [provider, reason] of Object.entries(queueResult.dispatch.ineligible)) {
|
|
14874
15157
|
if (!reason) continue;
|
|
14875
|
-
|
|
15158
|
+
log16.warn("run.dispatch-sync-fallback", {
|
|
14876
15159
|
runId,
|
|
14877
15160
|
projectName: project.name,
|
|
14878
15161
|
provider,
|
|
@@ -14886,16 +15169,16 @@ var Scheduler = class {
|
|
|
14886
15169
|
lastRunAt: now,
|
|
14887
15170
|
nextRunAt
|
|
14888
15171
|
});
|
|
14889
|
-
|
|
15172
|
+
log16.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
|
|
14890
15173
|
this.callbacks.onRunCreated(runId, projectId, providers, resolvedLocation);
|
|
14891
15174
|
} catch (err) {
|
|
14892
|
-
|
|
15175
|
+
log16.error("trigger.error", { scheduleId, projectId, kind, error: describeError(err) });
|
|
14893
15176
|
}
|
|
14894
15177
|
}
|
|
14895
15178
|
};
|
|
14896
15179
|
|
|
14897
15180
|
// src/site-liveness-loop.ts
|
|
14898
|
-
var
|
|
15181
|
+
var log17 = createLogger("SiteLiveness");
|
|
14899
15182
|
var SITE_LIVENESS_INTERVAL_MS = 10 * 6e4;
|
|
14900
15183
|
async function runSiteLivenessPass(deps) {
|
|
14901
15184
|
const now = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
@@ -14910,10 +15193,10 @@ async function runSiteLivenessPass(deps) {
|
|
|
14910
15193
|
const event = await deps.notify(project.id, { check, checkedAt: now() });
|
|
14911
15194
|
if (event) {
|
|
14912
15195
|
events += 1;
|
|
14913
|
-
|
|
15196
|
+
log17.info("site-liveness.notified", { projectName: project.name, event, code: check.code });
|
|
14914
15197
|
}
|
|
14915
15198
|
} catch (err) {
|
|
14916
|
-
|
|
15199
|
+
log17.warn("site-liveness.project-failed", { projectName: project.name, err: String(err) });
|
|
14917
15200
|
}
|
|
14918
15201
|
}
|
|
14919
15202
|
return { checked, events };
|
|
@@ -14922,11 +15205,11 @@ function startSiteLivenessLoop(deps, intervalMs = SITE_LIVENESS_INTERVAL_MS) {
|
|
|
14922
15205
|
let running = false;
|
|
14923
15206
|
const timer = setInterval(() => {
|
|
14924
15207
|
if (running) {
|
|
14925
|
-
|
|
15208
|
+
log17.warn("site-liveness.pass-still-running", { intervalMs });
|
|
14926
15209
|
return;
|
|
14927
15210
|
}
|
|
14928
15211
|
running = true;
|
|
14929
|
-
void runSiteLivenessPass(deps).catch((err) =>
|
|
15212
|
+
void runSiteLivenessPass(deps).catch((err) => log17.warn("site-liveness.pass-failed", { err: String(err) })).finally(() => {
|
|
14930
15213
|
running = false;
|
|
14931
15214
|
});
|
|
14932
15215
|
}, intervalMs);
|
|
@@ -14936,7 +15219,7 @@ function startSiteLivenessLoop(deps, intervalMs = SITE_LIVENESS_INTERVAL_MS) {
|
|
|
14936
15219
|
|
|
14937
15220
|
// src/provider-batch-poller.ts
|
|
14938
15221
|
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
|
|
15222
|
+
var log18 = createLogger("ProviderBatchPoller");
|
|
14940
15223
|
var PROVIDER_BATCH_POLL_INITIAL_MS = 3e4;
|
|
14941
15224
|
var PROVIDER_BATCH_POLL_MAX_MS = 10 * 6e4;
|
|
14942
15225
|
var PROVIDER_BATCH_CANCEL_GRACE_MS = 60 * 6e4;
|
|
@@ -14960,7 +15243,7 @@ var ProviderBatchPoller = class {
|
|
|
14960
15243
|
try {
|
|
14961
15244
|
if (await this.advance(row)) settledRuns.set(row.runId, row.projectId);
|
|
14962
15245
|
} catch (err) {
|
|
14963
|
-
|
|
15246
|
+
log18.warn("batch.advance-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error: describeError(err) });
|
|
14964
15247
|
this.reschedule(row.id, null);
|
|
14965
15248
|
}
|
|
14966
15249
|
}
|
|
@@ -14969,7 +15252,7 @@ var ProviderBatchPoller = class {
|
|
|
14969
15252
|
try {
|
|
14970
15253
|
this.deps.runner.finalizeBatchRun(runId, projectId);
|
|
14971
15254
|
} catch (err) {
|
|
14972
|
-
|
|
15255
|
+
log18.warn("batch.finalize-failed", { runId, error: describeError(err) });
|
|
14973
15256
|
}
|
|
14974
15257
|
}
|
|
14975
15258
|
}
|
|
@@ -15000,10 +15283,10 @@ var ProviderBatchPoller = class {
|
|
|
15000
15283
|
return ended ? this.ingest({ ...row, status: ProviderBatchStatuses.ended, resultsExpireAt: poll.resultsExpireAt ?? null }) : false;
|
|
15001
15284
|
}
|
|
15002
15285
|
} catch (err) {
|
|
15003
|
-
|
|
15286
|
+
log18.warn("batch.poll-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error: describeError(err) });
|
|
15004
15287
|
}
|
|
15005
15288
|
} else {
|
|
15006
|
-
|
|
15289
|
+
log18.warn("batch.poll-unavailable", { batchId: row.id, runId: row.runId, provider: row.provider });
|
|
15007
15290
|
}
|
|
15008
15291
|
this.reschedule(row.id, status);
|
|
15009
15292
|
return this.enforceDeadline(row, registered);
|
|
@@ -15025,12 +15308,12 @@ var ProviderBatchPoller = class {
|
|
|
15025
15308
|
isNull2(providerBatches.cancelRequestedAt)
|
|
15026
15309
|
)).run().changes === 1;
|
|
15027
15310
|
if (!claimed) return false;
|
|
15028
|
-
|
|
15311
|
+
log18.warn("batch.deadline-passed", { batchId: row.id, runId: row.runId, provider: row.provider, deadlineAt: row.deadlineAt });
|
|
15029
15312
|
if (registered?.adapter.batch && row.providerBatchId) {
|
|
15030
15313
|
try {
|
|
15031
15314
|
await registered.adapter.batch.cancel(row.providerBatchId, registered.config);
|
|
15032
15315
|
} catch (err) {
|
|
15033
|
-
|
|
15316
|
+
log18.warn("batch.cancel-failed", { batchId: row.id, provider: row.provider, error: describeError(err) });
|
|
15034
15317
|
}
|
|
15035
15318
|
}
|
|
15036
15319
|
return false;
|
|
@@ -15042,7 +15325,7 @@ var ProviderBatchPoller = class {
|
|
|
15042
15325
|
updatedAt: at
|
|
15043
15326
|
}).where(and16(eq18(providerBatches.id, row.id), eq18(providerBatches.status, ProviderBatchStatuses.submitted))).run().changes === 1;
|
|
15044
15327
|
this.schedule.delete(row.id);
|
|
15045
|
-
if (gaveUp)
|
|
15328
|
+
if (gaveUp) log18.warn("batch.abandoned-after-deadline", { batchId: row.id, runId: row.runId, provider: row.provider });
|
|
15046
15329
|
return gaveUp;
|
|
15047
15330
|
}
|
|
15048
15331
|
async ingest(row) {
|
|
@@ -15051,7 +15334,7 @@ var ProviderBatchPoller = class {
|
|
|
15051
15334
|
return result2.kind !== "skipped";
|
|
15052
15335
|
} catch (err) {
|
|
15053
15336
|
const error = describeError(err);
|
|
15054
|
-
|
|
15337
|
+
log18.warn("batch.ingest-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error });
|
|
15055
15338
|
const now = this.now();
|
|
15056
15339
|
const unreadable = row.resultsExpireAt !== null && now >= Date.parse(row.resultsExpireAt);
|
|
15057
15340
|
if (unreadable || now >= Date.parse(row.deadlineAt) + PROVIDER_BATCH_CANCEL_GRACE_MS) {
|
|
@@ -15093,7 +15376,7 @@ function startProviderBatchPoller(deps, intervalMs = PROVIDER_BATCH_TICK_MS) {
|
|
|
15093
15376
|
const timer = setInterval(() => {
|
|
15094
15377
|
if (running) return;
|
|
15095
15378
|
running = true;
|
|
15096
|
-
void poller.tick().catch((err) =>
|
|
15379
|
+
void poller.tick().catch((err) => log18.warn("batch.pass-failed", { error: describeError(err) })).finally(() => {
|
|
15097
15380
|
running = false;
|
|
15098
15381
|
});
|
|
15099
15382
|
}, intervalMs);
|
|
@@ -15102,7 +15385,7 @@ function startProviderBatchPoller(deps, intervalMs = PROVIDER_BATCH_TICK_MS) {
|
|
|
15102
15385
|
}
|
|
15103
15386
|
|
|
15104
15387
|
// src/data-refresh.ts
|
|
15105
|
-
var
|
|
15388
|
+
var log19 = createLogger("DataRefresh");
|
|
15106
15389
|
function gaRefreshDays(now = /* @__PURE__ */ new Date()) {
|
|
15107
15390
|
const [reportMonth] = reportMonthsForDoctor(void 0, now);
|
|
15108
15391
|
const reach = inclusiveDayCount(`${reportMonth}-01`, now.toISOString().slice(0, 10)) ?? 0;
|
|
@@ -15124,11 +15407,11 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
|
|
|
15124
15407
|
results.forEach((result2, idx) => {
|
|
15125
15408
|
const integration = integrations[idx].name;
|
|
15126
15409
|
if (result2.status === "fulfilled") {
|
|
15127
|
-
|
|
15410
|
+
log19.info("integration.refreshed", { projectName, integration });
|
|
15128
15411
|
} else {
|
|
15129
15412
|
const reason = result2.reason;
|
|
15130
15413
|
const message = describeError(reason);
|
|
15131
|
-
|
|
15414
|
+
log19.warn("integration.refresh-failed", { projectName, integration, error: message });
|
|
15132
15415
|
}
|
|
15133
15416
|
});
|
|
15134
15417
|
}
|
|
@@ -15136,7 +15419,7 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
|
|
|
15136
15419
|
// src/notifier.ts
|
|
15137
15420
|
import { eq as eq19, desc as desc8, and as and17, inArray as inArray9, or } from "drizzle-orm";
|
|
15138
15421
|
import crypto22 from "crypto";
|
|
15139
|
-
var
|
|
15422
|
+
var log20 = createLogger("Notifier");
|
|
15140
15423
|
var MATERIAL_MAGNITUDE_DELTA = 20;
|
|
15141
15424
|
var SEVERITY_RANK = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
15142
15425
|
function severityRank(severity) {
|
|
@@ -15166,26 +15449,26 @@ var Notifier = class {
|
|
|
15166
15449
|
}
|
|
15167
15450
|
/** Called after a run completes (success, partial, or failed). */
|
|
15168
15451
|
async onRunCompleted(runId, projectId, opts) {
|
|
15169
|
-
|
|
15452
|
+
log20.info("run.completed", { runId, projectId, origin: opts?.origin ?? "sweep" });
|
|
15170
15453
|
const notifs = this.db.select().from(notifications).where(eq19(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
15171
15454
|
if (notifs.length === 0) {
|
|
15172
|
-
|
|
15455
|
+
log20.info("notifications.none-enabled", { projectId });
|
|
15173
15456
|
return;
|
|
15174
15457
|
}
|
|
15175
|
-
|
|
15458
|
+
log20.info("notifications.found", { projectId, count: notifs.length });
|
|
15176
15459
|
const run = this.db.select().from(runs).where(eq19(runs.id, runId)).get();
|
|
15177
15460
|
if (!run) {
|
|
15178
|
-
|
|
15461
|
+
log20.error("run.not-found", { runId, msg: "skipping notification dispatch" });
|
|
15179
15462
|
return;
|
|
15180
15463
|
}
|
|
15181
15464
|
const project = this.db.select().from(projects).where(eq19(projects.id, projectId)).get();
|
|
15182
15465
|
if (!project) {
|
|
15183
|
-
|
|
15466
|
+
log20.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
|
|
15184
15467
|
return;
|
|
15185
15468
|
}
|
|
15186
15469
|
const transitions = this.computeTransitions(runId, projectId);
|
|
15187
15470
|
const events = [];
|
|
15188
|
-
|
|
15471
|
+
log20.info("run.status", { runId: run.id, status: run.status, projectId });
|
|
15189
15472
|
if ((run.status === "completed" || run.status === "partial") && opts?.origin !== "fill") {
|
|
15190
15473
|
events.push("run.completed");
|
|
15191
15474
|
}
|
|
@@ -15201,7 +15484,7 @@ var Notifier = class {
|
|
|
15201
15484
|
if (!config.url) continue;
|
|
15202
15485
|
const subscribedEvents = config.events;
|
|
15203
15486
|
const matchingEvents = events.filter((e) => subscribedEvents.includes(e));
|
|
15204
|
-
|
|
15487
|
+
log20.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
|
|
15205
15488
|
if (matchingEvents.length === 0) continue;
|
|
15206
15489
|
for (const event of matchingEvents) {
|
|
15207
15490
|
const relevantTransitions = event === "citation.lost" ? lostTransitions : event === "citation.gained" ? gainedTransitions : transitions;
|
|
@@ -15233,7 +15516,7 @@ var Notifier = class {
|
|
|
15233
15516
|
async onHealthChecked(projectId, report) {
|
|
15234
15517
|
const project = this.db.select().from(projects).where(eq19(projects.id, projectId)).get();
|
|
15235
15518
|
if (!project) {
|
|
15236
|
-
|
|
15519
|
+
log20.error("project.not-found", { projectId, msg: "skipping health notification" });
|
|
15237
15520
|
return null;
|
|
15238
15521
|
}
|
|
15239
15522
|
const rank = (status2) => status2 === "fail" ? 2 : status2 === "warn" ? 1 : 0;
|
|
@@ -15285,7 +15568,7 @@ var Notifier = class {
|
|
|
15285
15568
|
this.db.update(doctorHealthState).set(observation).where(eq19(doctorHealthState.projectId, projectId)).run();
|
|
15286
15569
|
}
|
|
15287
15570
|
if (!event) {
|
|
15288
|
-
|
|
15571
|
+
log20.info("health.unchanged", { projectId, status, code });
|
|
15289
15572
|
return null;
|
|
15290
15573
|
}
|
|
15291
15574
|
const notifs = this.db.select().from(notifications).where(eq19(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
@@ -15313,7 +15596,7 @@ var Notifier = class {
|
|
|
15313
15596
|
if (delivered > 0) {
|
|
15314
15597
|
this.db.update(doctorHealthState).set({ notifiedAt: now }).where(eq19(doctorHealthState.projectId, projectId)).run();
|
|
15315
15598
|
}
|
|
15316
|
-
|
|
15599
|
+
log20.info("health.notified", { projectId, event, status, code, subscribers: notifs.length, delivered });
|
|
15317
15600
|
return event;
|
|
15318
15601
|
}
|
|
15319
15602
|
/**
|
|
@@ -15334,7 +15617,7 @@ var Notifier = class {
|
|
|
15334
15617
|
if (check.status !== "ok" && check.status !== "fail") return null;
|
|
15335
15618
|
const project = this.db.select().from(projects).where(eq19(projects.id, projectId)).get();
|
|
15336
15619
|
if (!project) {
|
|
15337
|
-
|
|
15620
|
+
log20.error("project.not-found", { projectId, msg: "skipping site liveness notification" });
|
|
15338
15621
|
return null;
|
|
15339
15622
|
}
|
|
15340
15623
|
const previous = this.db.select().from(siteLivenessState).where(eq19(siteLivenessState.projectId, projectId)).get();
|
|
@@ -15388,7 +15671,7 @@ var Notifier = class {
|
|
|
15388
15671
|
if (event === "health.degraded" && delivered > 0) {
|
|
15389
15672
|
this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(eq19(siteLivenessState.projectId, projectId)).run();
|
|
15390
15673
|
}
|
|
15391
|
-
|
|
15674
|
+
log20.info("site-liveness.notified", { projectId, event, code: check.code, subscribers: notifs.length, delivered });
|
|
15392
15675
|
return event;
|
|
15393
15676
|
}
|
|
15394
15677
|
/**
|
|
@@ -15625,7 +15908,7 @@ var Notifier = class {
|
|
|
15625
15908
|
delivered += accepted;
|
|
15626
15909
|
}
|
|
15627
15910
|
}
|
|
15628
|
-
|
|
15911
|
+
log20.info("reviews.dispatched", {
|
|
15629
15912
|
projectId,
|
|
15630
15913
|
reviews: reviewsToSend.length,
|
|
15631
15914
|
ratingDrops: ratingsToSend.length,
|
|
@@ -15638,7 +15921,7 @@ var Notifier = class {
|
|
|
15638
15921
|
if (!thisRun) return [];
|
|
15639
15922
|
const completeness = measurementRunCompleteness(this.db, runId);
|
|
15640
15923
|
if (completeness.planned && !completeness.complete) {
|
|
15641
|
-
|
|
15924
|
+
log20.info("transitions.skipped-incomplete", {
|
|
15642
15925
|
runId,
|
|
15643
15926
|
executed: completeness.executed,
|
|
15644
15927
|
expected: completeness.expected
|
|
@@ -15730,23 +16013,23 @@ var Notifier = class {
|
|
|
15730
16013
|
const targetLabel = redactNotificationUrl(url).urlDisplay;
|
|
15731
16014
|
const targetCheck = await resolveWebhookTarget(url);
|
|
15732
16015
|
if (!targetCheck.ok) {
|
|
15733
|
-
|
|
16016
|
+
log20.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
|
|
15734
16017
|
this.logDelivery(projectId, notificationId, payload.event, "failed", `SSRF: ${targetCheck.message}`);
|
|
15735
16018
|
return false;
|
|
15736
16019
|
}
|
|
15737
|
-
|
|
16020
|
+
log20.info("webhook.send", { event: payload.event, url: targetLabel });
|
|
15738
16021
|
const maxRetries = 3;
|
|
15739
16022
|
const delays = [1e3, 4e3, 16e3];
|
|
15740
16023
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
15741
16024
|
try {
|
|
15742
16025
|
const response = await deliverWebhook(targetCheck.target, body, signingSecret);
|
|
15743
16026
|
if (response.status >= 200 && response.status < 300) {
|
|
15744
|
-
|
|
16027
|
+
log20.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
|
|
15745
16028
|
this.logDelivery(projectId, notificationId, payload.event, "sent", null);
|
|
15746
16029
|
return true;
|
|
15747
16030
|
}
|
|
15748
16031
|
const errorDetail = response.error ?? `HTTP ${response.status}`;
|
|
15749
|
-
|
|
16032
|
+
log20.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
|
|
15750
16033
|
if (attempt === maxRetries - 1) {
|
|
15751
16034
|
this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
|
|
15752
16035
|
}
|
|
@@ -15754,7 +16037,7 @@ var Notifier = class {
|
|
|
15754
16037
|
const errorDetail = describeError(err);
|
|
15755
16038
|
if (attempt === maxRetries - 1) {
|
|
15756
16039
|
this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
|
|
15757
|
-
|
|
16040
|
+
log20.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
|
|
15758
16041
|
}
|
|
15759
16042
|
}
|
|
15760
16043
|
if (attempt < maxRetries - 1) {
|
|
@@ -15779,7 +16062,7 @@ var Notifier = class {
|
|
|
15779
16062
|
|
|
15780
16063
|
// src/run-coordinator.ts
|
|
15781
16064
|
import { eq as eq20 } from "drizzle-orm";
|
|
15782
|
-
var
|
|
16065
|
+
var log21 = createLogger("RunCoordinator");
|
|
15783
16066
|
var RunCoordinator = class {
|
|
15784
16067
|
constructor(db, notifier, intelligenceService, onInsightsGenerated, onAeroEvent) {
|
|
15785
16068
|
this.db = db;
|
|
@@ -15795,16 +16078,31 @@ var RunCoordinator = class {
|
|
|
15795
16078
|
onAeroEvent;
|
|
15796
16079
|
/** Host wakeup only; durable completion receipts remain the delivery authority. */
|
|
15797
16080
|
onSentimentCompleted;
|
|
16081
|
+
/**
|
|
16082
|
+
* Host hook after a non-probe answer-visibility run finishes `completed` or
|
|
16083
|
+
* `partial`: the run stored new answers, so answer-derived competitor
|
|
16084
|
+
* aliases may have new evidence. The local server schedules a detection pass
|
|
16085
|
+
* off the completion path (`createCompetitorAutoAliasRunner`). Failures are
|
|
16086
|
+
* logged and never reach the other subscribers.
|
|
16087
|
+
*/
|
|
16088
|
+
onAnswersRecorded;
|
|
15798
16089
|
async onRunCompleted(runId, projectId, opts) {
|
|
15799
16090
|
const runRow = this.db.select().from(runs).where(eq20(runs.id, runId)).get();
|
|
15800
16091
|
const kind = runRow?.kind ?? RunKinds["answer-visibility"];
|
|
15801
16092
|
if (runRow?.trigger === RunTriggers.probe) {
|
|
15802
|
-
|
|
16093
|
+
log21.info("probe.skip-side-effects", { runId, projectId, kind });
|
|
15803
16094
|
return;
|
|
15804
16095
|
}
|
|
16096
|
+
if (kind === RunKinds["answer-visibility"] && (runRow?.status === RunStatuses.completed || runRow?.status === RunStatuses.partial) && this.onAnswersRecorded) {
|
|
16097
|
+
try {
|
|
16098
|
+
await this.onAnswersRecorded(runId, projectId);
|
|
16099
|
+
} catch (err) {
|
|
16100
|
+
log21.error("answers-recorded.failed", { runId, projectId, error: describeError(err) });
|
|
16101
|
+
}
|
|
16102
|
+
}
|
|
15805
16103
|
const completeness = measurementRunCompleteness(this.db, runId);
|
|
15806
16104
|
if (completeness.planned && !completeness.complete) {
|
|
15807
|
-
|
|
16105
|
+
log21.info("measurement.incomplete-skip-derived", {
|
|
15808
16106
|
runId,
|
|
15809
16107
|
projectId,
|
|
15810
16108
|
executed: completeness.executed,
|
|
@@ -15813,7 +16111,7 @@ var RunCoordinator = class {
|
|
|
15813
16111
|
try {
|
|
15814
16112
|
await this.notifier.onRunCompleted(runId, projectId);
|
|
15815
16113
|
} catch (err) {
|
|
15816
|
-
|
|
16114
|
+
log21.error("notifier.failed", { runId, error: describeError(err) });
|
|
15817
16115
|
}
|
|
15818
16116
|
return;
|
|
15819
16117
|
}
|
|
@@ -15821,7 +16119,7 @@ var RunCoordinator = class {
|
|
|
15821
16119
|
try {
|
|
15822
16120
|
await this.onSentimentCompleted(runId, projectId);
|
|
15823
16121
|
} catch {
|
|
15824
|
-
|
|
16122
|
+
log21.error("sentiment.wakeup-failed", { runId, projectId });
|
|
15825
16123
|
}
|
|
15826
16124
|
}
|
|
15827
16125
|
let insightCount = 0;
|
|
@@ -15838,12 +16136,12 @@ var RunCoordinator = class {
|
|
|
15838
16136
|
try {
|
|
15839
16137
|
await this.onInsightsGenerated(runId, projectId, result2);
|
|
15840
16138
|
} catch (err) {
|
|
15841
|
-
|
|
16139
|
+
log21.error("insight-webhook.failed", { runId, error: describeError(err) });
|
|
15842
16140
|
}
|
|
15843
16141
|
}
|
|
15844
16142
|
}
|
|
15845
16143
|
} catch (err) {
|
|
15846
|
-
|
|
16144
|
+
log21.error("intelligence.failed", { runId, error: describeError(err) });
|
|
15847
16145
|
}
|
|
15848
16146
|
} else if (kind === RunKinds["gbp-sync"]) {
|
|
15849
16147
|
try {
|
|
@@ -15856,22 +16154,22 @@ var RunCoordinator = class {
|
|
|
15856
16154
|
try {
|
|
15857
16155
|
await this.onInsightsGenerated(runId, projectId, analysisResultFromInsights(gbpInsights));
|
|
15858
16156
|
} catch (err) {
|
|
15859
|
-
|
|
16157
|
+
log21.error("gbp-insight-webhook.failed", { runId, error: describeError(err) });
|
|
15860
16158
|
}
|
|
15861
16159
|
}
|
|
15862
16160
|
} catch (err) {
|
|
15863
|
-
|
|
16161
|
+
log21.error("gbp-intelligence.failed", { runId, error: describeError(err) });
|
|
15864
16162
|
}
|
|
15865
16163
|
try {
|
|
15866
16164
|
await this.notifier.dispatchReviewAlerts(projectId);
|
|
15867
16165
|
} catch (err) {
|
|
15868
|
-
|
|
16166
|
+
log21.error("review-webhook.failed", { runId, error: describeError(err) });
|
|
15869
16167
|
}
|
|
15870
16168
|
}
|
|
15871
16169
|
try {
|
|
15872
16170
|
await (opts ? this.notifier.onRunCompleted(runId, projectId, opts) : this.notifier.onRunCompleted(runId, projectId));
|
|
15873
16171
|
} catch (err) {
|
|
15874
|
-
|
|
16172
|
+
log21.error("notifier.failed", { runId, error: describeError(err) });
|
|
15875
16173
|
}
|
|
15876
16174
|
if (this.onAeroEvent) {
|
|
15877
16175
|
try {
|
|
@@ -15884,7 +16182,7 @@ var RunCoordinator = class {
|
|
|
15884
16182
|
};
|
|
15885
16183
|
await this.onAeroEvent(ctx);
|
|
15886
16184
|
} catch (err) {
|
|
15887
|
-
|
|
16185
|
+
log21.error("aero.failed", { runId, error: describeError(err) });
|
|
15888
16186
|
}
|
|
15889
16187
|
}
|
|
15890
16188
|
}
|
|
@@ -16289,7 +16587,7 @@ function responseSchemasFor(operations) {
|
|
|
16289
16587
|
return schema ? [schema] : [];
|
|
16290
16588
|
});
|
|
16291
16589
|
}
|
|
16292
|
-
function
|
|
16590
|
+
function isRecord7(value) {
|
|
16293
16591
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
16294
16592
|
}
|
|
16295
16593
|
function resolve(schema, components) {
|
|
@@ -16322,7 +16620,7 @@ function render(schema, value, components, depth) {
|
|
|
16322
16620
|
}
|
|
16323
16621
|
let current = value;
|
|
16324
16622
|
for (const member of members(node)) current = render(member, current, components, depth + 1);
|
|
16325
|
-
if (Array.isArray(current) &&
|
|
16623
|
+
if (Array.isArray(current) && isRecord7(node.items)) {
|
|
16326
16624
|
const items = node.items;
|
|
16327
16625
|
let changed = false;
|
|
16328
16626
|
const out = current.map((item) => {
|
|
@@ -16332,9 +16630,9 @@ function render(schema, value, components, depth) {
|
|
|
16332
16630
|
});
|
|
16333
16631
|
return changed ? out : current;
|
|
16334
16632
|
}
|
|
16335
|
-
if (
|
|
16336
|
-
const properties =
|
|
16337
|
-
const extra =
|
|
16633
|
+
if (isRecord7(current)) {
|
|
16634
|
+
const properties = isRecord7(node.properties) ? node.properties : {};
|
|
16635
|
+
const extra = isRecord7(node.additionalProperties) ? node.additionalProperties : void 0;
|
|
16338
16636
|
let changed = false;
|
|
16339
16637
|
const out = {};
|
|
16340
16638
|
for (const [key, child] of Object.entries(current)) {
|
|
@@ -16380,7 +16678,7 @@ var MAX_PARTIAL_NOTES = 12;
|
|
|
16380
16678
|
function serializeResult(value) {
|
|
16381
16679
|
return JSON.stringify(value);
|
|
16382
16680
|
}
|
|
16383
|
-
function
|
|
16681
|
+
function isRecord8(value) {
|
|
16384
16682
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
16385
16683
|
}
|
|
16386
16684
|
function keptSummary(counts, cursors = {}) {
|
|
@@ -16400,7 +16698,7 @@ function noteCursor(owner, ownerPath, listPath, kept, total, notes, root = owner
|
|
|
16400
16698
|
}
|
|
16401
16699
|
function cursorForList(owner, ownerPath, listKey, root = owner) {
|
|
16402
16700
|
const prefix = ownerPath ? `${ownerPath}.` : "";
|
|
16403
|
-
if (listKey === "queries" &&
|
|
16701
|
+
if (listKey === "queries" && isRecord8(owner.queryPage) && owner.queryPage.nextCursor !== void 0 && owner.queryPage.nextCursor !== null) {
|
|
16404
16702
|
const page = owner.queryPage;
|
|
16405
16703
|
return {
|
|
16406
16704
|
path: `${prefix}queryPage.nextCursor`,
|
|
@@ -16477,7 +16775,7 @@ function partialLists(root) {
|
|
|
16477
16775
|
if (count3.count <= rows.length) continue;
|
|
16478
16776
|
notes.push([label, `${rows.length} of ${count3.text}`]);
|
|
16479
16777
|
noted = true;
|
|
16480
|
-
} else if (ownFlag === true || object.truncated === true && typeof ownFlag !== "boolean" && (lists.length === 1 || rows.some(
|
|
16778
|
+
} else if (ownFlag === true || object.truncated === true && typeof ownFlag !== "boolean" && (lists.length === 1 || rows.some(isRecord8))) {
|
|
16481
16779
|
notes.push([label, `${rows.length} shown; the tool cut this list`]);
|
|
16482
16780
|
noted = true;
|
|
16483
16781
|
}
|
|
@@ -16486,17 +16784,17 @@ function partialLists(root) {
|
|
|
16486
16784
|
if (object.truncated === true && !noted) notes.push([path9 || "(root)", "the tool cut the lists here"]);
|
|
16487
16785
|
if (depth >= MAX_PARTIAL_DEPTH || notes.length >= MAX_PARTIAL_NOTES) return;
|
|
16488
16786
|
for (const [key, value] of Object.entries(object)) {
|
|
16489
|
-
if (!key.startsWith("__") &&
|
|
16787
|
+
if (!key.startsWith("__") && isRecord8(value)) visit(value, path9 ? `${path9}.${key}` : key, depth + 1);
|
|
16490
16788
|
}
|
|
16491
16789
|
};
|
|
16492
16790
|
visit(root, "", 1);
|
|
16493
16791
|
return notes.length > 0 ? Object.fromEntries(notes.slice(0, MAX_PARTIAL_NOTES)) : void 0;
|
|
16494
16792
|
}
|
|
16495
16793
|
function withPartialLists(value) {
|
|
16496
|
-
if (!
|
|
16794
|
+
if (!isRecord8(value) || typeof value.toJSON === "function") return value;
|
|
16497
16795
|
const hadNote = Object.hasOwn(value, PARTIAL_LISTS_KEY);
|
|
16498
16796
|
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) =>
|
|
16797
|
+
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
16798
|
const pagination = {};
|
|
16501
16799
|
const visit = (owner, path9, depth) => {
|
|
16502
16800
|
const lists = Object.entries(owner).filter((entry) => Array.isArray(entry[1]));
|
|
@@ -16506,11 +16804,11 @@ function withPartialLists(value) {
|
|
|
16506
16804
|
if (!cursor || key.startsWith("__") || Object.keys(pagination).length >= MAX_PARTIAL_NOTES) continue;
|
|
16507
16805
|
const label = path9 ? `${path9}.${key}` : key;
|
|
16508
16806
|
const total = cursor.total ?? ownListTotal(numbers, key) ?? (lists.length === 1 ? bareTotal(numbers) : void 0);
|
|
16509
|
-
const incomplete = owner.__truncated === true ||
|
|
16807
|
+
const incomplete = owner.__truncated === true || isRecord8(owner.__omittedRowsByField) && typeof owner.__omittedRowsByField[key] === "number";
|
|
16510
16808
|
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
16809
|
}
|
|
16512
16810
|
if (depth >= MAX_PARTIAL_DEPTH) return;
|
|
16513
|
-
for (const [key, child] of Object.entries(owner)) if (!key.startsWith("__") &&
|
|
16811
|
+
for (const [key, child] of Object.entries(owner)) if (!key.startsWith("__") && isRecord8(child)) visit(child, path9 ? `${path9}.${key}` : key, depth + 1);
|
|
16514
16812
|
};
|
|
16515
16813
|
visit(value, "", 1);
|
|
16516
16814
|
const { [PARTIAL_LISTS_KEY]: _previous, __pagination: _pagination, ...rest } = value;
|
|
@@ -16563,7 +16861,7 @@ function listTrimmer(lists) {
|
|
|
16563
16861
|
hadTruncated: Object.hasOwn(owner, "__truncated"),
|
|
16564
16862
|
priorTruncated: owner.__truncated,
|
|
16565
16863
|
previous,
|
|
16566
|
-
priorOmitted:
|
|
16864
|
+
priorOmitted: isRecord8(previous) ? previous : void 0
|
|
16567
16865
|
};
|
|
16568
16866
|
});
|
|
16569
16867
|
return (counts) => {
|
|
@@ -16599,12 +16897,12 @@ function collectCollections(root) {
|
|
|
16599
16897
|
if (Array.isArray(value)) {
|
|
16600
16898
|
let records = false;
|
|
16601
16899
|
for (const row of value) {
|
|
16602
|
-
if (!
|
|
16900
|
+
if (!isRecord8(row)) continue;
|
|
16603
16901
|
records = true;
|
|
16604
16902
|
visit(row, `${valuePath}[]`, true);
|
|
16605
16903
|
}
|
|
16606
16904
|
if (value.length > 0) found.push({ path: valuePath, nested, records, owner: object, key, rows: value });
|
|
16607
|
-
} else if (
|
|
16905
|
+
} else if (isRecord8(value)) {
|
|
16608
16906
|
visit(value, valuePath, nested);
|
|
16609
16907
|
}
|
|
16610
16908
|
}
|
|
@@ -16641,8 +16939,8 @@ function currentSummary(root, owned) {
|
|
|
16641
16939
|
counts.set(list.path, count3);
|
|
16642
16940
|
noteCursor(object, path9, list.path, value.length, list.rows.length, cursors, root);
|
|
16643
16941
|
}
|
|
16644
|
-
for (const row of value) if (
|
|
16645
|
-
} else if (
|
|
16942
|
+
for (const row of value) if (isRecord8(row)) visit(row, `${valuePath}[]`);
|
|
16943
|
+
} else if (isRecord8(value)) {
|
|
16646
16944
|
visit(value, valuePath);
|
|
16647
16945
|
}
|
|
16648
16946
|
}
|
|
@@ -16655,7 +16953,7 @@ function emptiedFloorChars(root) {
|
|
|
16655
16953
|
}
|
|
16656
16954
|
function trimNestedArrays(full) {
|
|
16657
16955
|
const copy = JSON.parse(full);
|
|
16658
|
-
if (!
|
|
16956
|
+
if (!isRecord8(copy)) return void 0;
|
|
16659
16957
|
copy.__truncated = true;
|
|
16660
16958
|
copy[TRUNCATION_SUMMARY_KEY] = keptSummary(/* @__PURE__ */ new Map());
|
|
16661
16959
|
if (emptiedFloorChars(copy) > MAX_TOOL_RESULT_CHARS) return void 0;
|
|
@@ -16726,7 +17024,7 @@ function trimNestedArrays(full) {
|
|
|
16726
17024
|
function keptRowTotal(serialized) {
|
|
16727
17025
|
const count3 = (value) => {
|
|
16728
17026
|
if (Array.isArray(value)) return value.length;
|
|
16729
|
-
if (!
|
|
17027
|
+
if (!isRecord8(value)) return 0;
|
|
16730
17028
|
return Object.entries(value).reduce((sum, [key, child]) => key.startsWith("__") ? sum : sum + count3(child), 0);
|
|
16731
17029
|
};
|
|
16732
17030
|
try {
|
|
@@ -16738,9 +17036,9 @@ function keptRowTotal(serialized) {
|
|
|
16738
17036
|
function hasOtherLists(value, primary) {
|
|
16739
17037
|
if (Array.isArray(value)) {
|
|
16740
17038
|
if (value !== primary && value.length > 0) return true;
|
|
16741
|
-
return value.some((row) => (
|
|
17039
|
+
return value.some((row) => (isRecord8(row) || Array.isArray(row)) && hasOtherLists(row, primary));
|
|
16742
17040
|
}
|
|
16743
|
-
return
|
|
17041
|
+
return isRecord8(value) && Object.values(value).some((child) => hasOtherLists(child, primary));
|
|
16744
17042
|
}
|
|
16745
17043
|
function projectionPath(path9) {
|
|
16746
17044
|
if (path9.length <= MAX_SUMMARY_PATH_CHARS) return path9;
|
|
@@ -16810,7 +17108,7 @@ function boundedProjection(full, partialRows, reserve = 0) {
|
|
|
16810
17108
|
if (rows.length === 0) omit(path9 || "items");
|
|
16811
17109
|
if (owner) {
|
|
16812
17110
|
const cursors = {};
|
|
16813
|
-
noteCursor(owner, ownerPath, path9, rows.length, input.length, cursors,
|
|
17111
|
+
noteCursor(owner, ownerPath, path9, rows.length, input.length, cursors, isRecord8(value) ? value : owner);
|
|
16814
17112
|
for (const [cursor, note] of Object.entries(cursors)) {
|
|
16815
17113
|
record2(summary.cursors ??= {}, cursor, note.replace(path9, projectionPath(path9)));
|
|
16816
17114
|
}
|
|
@@ -16818,7 +17116,7 @@ function boundedProjection(full, partialRows, reserve = 0) {
|
|
|
16818
17116
|
}
|
|
16819
17117
|
return `[${rows.join(",")}]`;
|
|
16820
17118
|
}
|
|
16821
|
-
if (!
|
|
17119
|
+
if (!isRecord8(input)) {
|
|
16822
17120
|
const text = serializeResult(input);
|
|
16823
17121
|
if (text.length <= budget) return text;
|
|
16824
17122
|
if (typeof input === "string" && budget > 2) {
|
|
@@ -16829,7 +17127,7 @@ function boundedProjection(full, partialRows, reserve = 0) {
|
|
|
16829
17127
|
omit(path9 || "(root)");
|
|
16830
17128
|
return void 0;
|
|
16831
17129
|
}
|
|
16832
|
-
const priority = ([key, child]) => key === "queryPage" &&
|
|
17130
|
+
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;
|
|
16833
17131
|
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);
|
|
16834
17132
|
const shown2 = [];
|
|
16835
17133
|
let used = 2;
|
|
@@ -16851,13 +17149,13 @@ function boundedProjection(full, partialRows, reserve = 0) {
|
|
|
16851
17149
|
};
|
|
16852
17150
|
const projected = project(value, MAX_TOOL_RESULT_CHARS - PROJECTION_SUMMARY_CHARS - reserve, "");
|
|
16853
17151
|
const shown = projected === void 0 ? {} : JSON.parse(projected);
|
|
16854
|
-
const output =
|
|
17152
|
+
const output = isRecord8(shown) ? shown : { items: shown };
|
|
16855
17153
|
const result2 = { ...output, __truncated: true, [TRUNCATION_SUMMARY_KEY]: summary };
|
|
16856
17154
|
return serializeResult(partialRows ? withPartialLists(result2) : result2);
|
|
16857
17155
|
}
|
|
16858
17156
|
function rowsShown(cut, path9) {
|
|
16859
17157
|
if (path9.includes("[")) return Infinity;
|
|
16860
|
-
const list = path9.split(".").reduce((owner, key) =>
|
|
17158
|
+
const list = path9.split(".").reduce((owner, key) => isRecord8(owner) ? owner[key] : void 0, cut);
|
|
16861
17159
|
return Array.isArray(list) ? list.length : 0;
|
|
16862
17160
|
}
|
|
16863
17161
|
function withPartialRows(full, whole) {
|
|
@@ -16999,6 +17297,12 @@ var AERO_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
|
|
|
16999
17297
|
// start paid classifier work and remain operator actions.
|
|
17000
17298
|
"canonry_sentiment_configure",
|
|
17001
17299
|
"canonry_sentiment_backfill",
|
|
17300
|
+
// Applying answer-derived competitor names changes the measured competitor
|
|
17301
|
+
// identity and restates stored history. Aero may preview detection
|
|
17302
|
+
// (`canonry_competitors_auto_aliases_detect`); applying stays an operator action.
|
|
17303
|
+
// The project writes that set the mode refuse a change instead; see
|
|
17304
|
+
// `refuseCompetitorAutoAliasModeChange`.
|
|
17305
|
+
CanonryMcpToolNames.canonry_competitors_auto_aliases_apply,
|
|
17002
17306
|
CanonryMcpToolNames.canonry_agent_clear,
|
|
17003
17307
|
CanonryMcpToolNames.canonry_agent_conversations_new,
|
|
17004
17308
|
CanonryMcpToolNames.canonry_agent_conversations_resume,
|
|
@@ -17027,9 +17331,48 @@ function refuseManagedSweepCancel(tool, ctx) {
|
|
|
17027
17331
|
}
|
|
17028
17332
|
};
|
|
17029
17333
|
}
|
|
17334
|
+
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.";
|
|
17335
|
+
var COMPETITOR_AUTO_ALIAS_MODE_WRITES = /* @__PURE__ */ new Set([
|
|
17336
|
+
CanonryMcpToolNames.canonry_project_upsert,
|
|
17337
|
+
CanonryMcpToolNames.canonry_apply_config
|
|
17338
|
+
]);
|
|
17339
|
+
function requestedCompetitorAutoAliasMode(toolName, params, ctx) {
|
|
17340
|
+
if (!isRecord8(params)) return { project: void 0, mode: void 0 };
|
|
17341
|
+
if (toolName === CanonryMcpToolNames.canonry_project_upsert) {
|
|
17342
|
+
const request = isRecord8(params.request) ? params.request : {};
|
|
17343
|
+
return { project: ctx.projectName, mode: request.competitorAutoAliases };
|
|
17344
|
+
}
|
|
17345
|
+
const config = isRecord8(params.config) ? params.config : {};
|
|
17346
|
+
const metadata2 = isRecord8(config.metadata) ? config.metadata : {};
|
|
17347
|
+
const spec = isRecord8(config.spec) ? config.spec : {};
|
|
17348
|
+
return { project: metadata2.name, mode: spec.competitorAutoAliases };
|
|
17349
|
+
}
|
|
17350
|
+
async function storedCompetitorAutoAliasMode(client, project) {
|
|
17351
|
+
if (typeof project !== "string") return DEFAULT_COMPETITOR_AUTO_ALIAS_MODE;
|
|
17352
|
+
try {
|
|
17353
|
+
return (await client.getProject(project)).competitorAutoAliases ?? DEFAULT_COMPETITOR_AUTO_ALIAS_MODE;
|
|
17354
|
+
} catch (err) {
|
|
17355
|
+
if (err instanceof CliError && err.code === "NOT_FOUND") return DEFAULT_COMPETITOR_AUTO_ALIAS_MODE;
|
|
17356
|
+
throw err;
|
|
17357
|
+
}
|
|
17358
|
+
}
|
|
17359
|
+
function refuseCompetitorAutoAliasModeChange(tool, ctx) {
|
|
17360
|
+
const write = tool.execute;
|
|
17361
|
+
return {
|
|
17362
|
+
...tool,
|
|
17363
|
+
execute: async (toolCallId, params, ...rest) => {
|
|
17364
|
+
const { project, mode } = requestedCompetitorAutoAliasMode(tool.name, params, ctx);
|
|
17365
|
+
if (mode !== void 0 && mode !== await storedCompetitorAutoAliasMode(ctx.client, project)) {
|
|
17366
|
+
throw new Error(COMPETITOR_AUTO_ALIAS_MODE_REFUSAL);
|
|
17367
|
+
}
|
|
17368
|
+
return write(toolCallId, params, ...rest);
|
|
17369
|
+
}
|
|
17370
|
+
};
|
|
17371
|
+
}
|
|
17030
17372
|
function buildMcpAgentTools(registry, ctx, opts = {}) {
|
|
17031
17373
|
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) => {
|
|
17032
17374
|
const agentTool = mcpToAgentTool(tool, ctx);
|
|
17375
|
+
if (COMPETITOR_AUTO_ALIAS_MODE_WRITES.has(tool.name)) return refuseCompetitorAutoAliasModeChange(agentTool, ctx);
|
|
17033
17376
|
return opts.managedSweeps && tool.name === CanonryMcpToolNames.canonry_run_cancel ? refuseManagedSweepCancel(agentTool, ctx) : agentTool;
|
|
17034
17377
|
});
|
|
17035
17378
|
}
|
|
@@ -17360,11 +17703,11 @@ function recordLlmUsageEvent(args) {
|
|
|
17360
17703
|
var AERO_MEMORY_SEPARATOR = "\n\n---\n\n";
|
|
17361
17704
|
var AERO_MEMORY_BLOCK_PREFIX = `${AERO_MEMORY_SEPARATOR}<memory>
|
|
17362
17705
|
`;
|
|
17363
|
-
function
|
|
17706
|
+
function isRecord9(value) {
|
|
17364
17707
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
17365
17708
|
}
|
|
17366
17709
|
function isTextSystemBlock(value) {
|
|
17367
|
-
return
|
|
17710
|
+
return isRecord9(value) && value.type === "text" && typeof value.text === "string";
|
|
17368
17711
|
}
|
|
17369
17712
|
function splitAeroHydratedSystemPrompt(systemPrompt) {
|
|
17370
17713
|
const idx = systemPrompt.lastIndexOf(AERO_MEMORY_BLOCK_PREFIX);
|
|
@@ -17376,7 +17719,7 @@ function splitAeroHydratedSystemPrompt(systemPrompt) {
|
|
|
17376
17719
|
}
|
|
17377
17720
|
function splitAeroAnthropicSystemCachePayload(payload, model) {
|
|
17378
17721
|
if (model.api !== "anthropic-messages") return void 0;
|
|
17379
|
-
if (!
|
|
17722
|
+
if (!isRecord9(payload) || !Array.isArray(payload.system)) return void 0;
|
|
17380
17723
|
let changed = false;
|
|
17381
17724
|
const nextSystem = [];
|
|
17382
17725
|
for (const block of payload.system) {
|
|
@@ -18136,7 +18479,7 @@ function resolveSessionProviderAndModel(config, opts) {
|
|
|
18136
18479
|
import { Type as Type6 } from "@earendil-works/pi-ai";
|
|
18137
18480
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
18138
18481
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
18139
|
-
var
|
|
18482
|
+
var log22 = createLogger("RemoteMcp");
|
|
18140
18483
|
var AERO_EXCLUDED_MCP_TOOLS2 = /* @__PURE__ */ new Set([]);
|
|
18141
18484
|
async function connectStreamableHttp(server) {
|
|
18142
18485
|
const transport = new StreamableHTTPClientTransport(new URL(server.url), {
|
|
@@ -18204,7 +18547,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
18204
18547
|
try {
|
|
18205
18548
|
client = await connect(server);
|
|
18206
18549
|
} catch (err) {
|
|
18207
|
-
|
|
18550
|
+
log22.error("external-mcp.connect-failed", {
|
|
18208
18551
|
label,
|
|
18209
18552
|
error: describeError(err)
|
|
18210
18553
|
});
|
|
@@ -18214,7 +18557,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
18214
18557
|
try {
|
|
18215
18558
|
listed = await client.listTools();
|
|
18216
18559
|
} catch (err) {
|
|
18217
|
-
|
|
18560
|
+
log22.error("external-mcp.list-failed", {
|
|
18218
18561
|
label,
|
|
18219
18562
|
error: describeError(err)
|
|
18220
18563
|
});
|
|
@@ -18225,7 +18568,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
18225
18568
|
if (!isReadOnly(tool)) continue;
|
|
18226
18569
|
tools.push(adaptRemoteTool(client, tool));
|
|
18227
18570
|
}
|
|
18228
|
-
|
|
18571
|
+
log22.info("external-mcp.loaded", {
|
|
18229
18572
|
label,
|
|
18230
18573
|
discovered: listed.tools.length,
|
|
18231
18574
|
adopted: tools.length
|
|
@@ -18533,7 +18876,7 @@ async function compactMessages(args) {
|
|
|
18533
18876
|
}
|
|
18534
18877
|
|
|
18535
18878
|
// src/agent/session-registry.ts
|
|
18536
|
-
var
|
|
18879
|
+
var log23 = createLogger("SessionRegistry");
|
|
18537
18880
|
var MAX_HYDRATE_NOTES = 20;
|
|
18538
18881
|
var MAX_HYDRATE_BYTES = 32 * 1024;
|
|
18539
18882
|
function escapeMemoryFragment(value) {
|
|
@@ -18592,7 +18935,7 @@ var SessionRegistry = class {
|
|
|
18592
18935
|
if (!this.externalToolsPromise) {
|
|
18593
18936
|
this.externalToolsPromise = loadExternalMcpTools(this.opts.config.externalMcpServers).catch(
|
|
18594
18937
|
(err) => {
|
|
18595
|
-
|
|
18938
|
+
log23.error("external-mcp.load-failed", {
|
|
18596
18939
|
error: describeError(err)
|
|
18597
18940
|
});
|
|
18598
18941
|
return [];
|
|
@@ -18866,13 +19209,13 @@ ${lines.join("\n")}
|
|
|
18866
19209
|
agent.state.messages = result2.messages;
|
|
18867
19210
|
setAeroSystemPrompt(agent, this.buildHydratedSystemPrompt(projectId, row.systemPrompt));
|
|
18868
19211
|
this.save(projectName);
|
|
18869
|
-
|
|
19212
|
+
log23.info("compaction.completed", {
|
|
18870
19213
|
projectName,
|
|
18871
19214
|
removedCount: result2.removedCount,
|
|
18872
19215
|
summaryBytes: Buffer.byteLength(result2.summary, "utf8")
|
|
18873
19216
|
});
|
|
18874
19217
|
} catch (err) {
|
|
18875
|
-
|
|
19218
|
+
log23.error("compaction.failed", {
|
|
18876
19219
|
projectName,
|
|
18877
19220
|
error: describeError(err)
|
|
18878
19221
|
});
|
|
@@ -19023,7 +19366,7 @@ ${lines.join("\n")}
|
|
|
19023
19366
|
await agent.prompt(msgs);
|
|
19024
19367
|
this.save(projectName);
|
|
19025
19368
|
} catch (err) {
|
|
19026
|
-
|
|
19369
|
+
log23.error("drain.failed", {
|
|
19027
19370
|
projectName,
|
|
19028
19371
|
error: describeError(err)
|
|
19029
19372
|
});
|
|
@@ -19337,7 +19680,10 @@ var ViewerAeroSessions = class {
|
|
|
19337
19680
|
this.opts.db.delete(apiKeys).where(eq24(apiKeys.id, id)).run();
|
|
19338
19681
|
} catch {
|
|
19339
19682
|
try {
|
|
19340
|
-
this.opts.db.
|
|
19683
|
+
this.opts.db.transaction((tx) => {
|
|
19684
|
+
tx.delete(dashboardSessions).where(eq24(dashboardSessions.apiKeyId, id)).run();
|
|
19685
|
+
tx.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(eq24(apiKeys.id, id)).run();
|
|
19686
|
+
});
|
|
19341
19687
|
} catch {
|
|
19342
19688
|
}
|
|
19343
19689
|
}
|
|
@@ -19386,7 +19732,10 @@ function mintSessionKey(db, scopes, userId) {
|
|
|
19386
19732
|
}
|
|
19387
19733
|
function revokeSessionKey(db, id) {
|
|
19388
19734
|
try {
|
|
19389
|
-
db.
|
|
19735
|
+
db.transaction((tx) => {
|
|
19736
|
+
tx.delete(dashboardSessions).where(eq25(dashboardSessions.apiKeyId, id)).run();
|
|
19737
|
+
tx.update(apiKeys).set({ revokedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq25(apiKeys.id, id)).run();
|
|
19738
|
+
});
|
|
19390
19739
|
} catch {
|
|
19391
19740
|
}
|
|
19392
19741
|
}
|
|
@@ -20189,7 +20538,7 @@ function formatAuditFactorScore(factor) {
|
|
|
20189
20538
|
}
|
|
20190
20539
|
|
|
20191
20540
|
// src/snapshot-service.ts
|
|
20192
|
-
var
|
|
20541
|
+
var log24 = createLogger("Snapshot");
|
|
20193
20542
|
var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "muse", "local"];
|
|
20194
20543
|
var SNAPSHOT_QUERY_COUNT = 6;
|
|
20195
20544
|
var SnapshotService = class {
|
|
@@ -20286,7 +20635,7 @@ var SnapshotService = class {
|
|
|
20286
20635
|
return mapAuditReport(report);
|
|
20287
20636
|
} catch (err) {
|
|
20288
20637
|
const message = describeError(err);
|
|
20289
|
-
|
|
20638
|
+
log24.warn("audit.failed", { homepageUrl, error: message });
|
|
20290
20639
|
return {
|
|
20291
20640
|
url: homepageUrl,
|
|
20292
20641
|
finalUrl: homepageUrl,
|
|
@@ -20315,7 +20664,7 @@ var SnapshotService = class {
|
|
|
20315
20664
|
queries: parsedQueries
|
|
20316
20665
|
};
|
|
20317
20666
|
} catch (err) {
|
|
20318
|
-
|
|
20667
|
+
log24.warn("profile.generation-failed", {
|
|
20319
20668
|
domain: ctx.domain,
|
|
20320
20669
|
provider: ctx.analysisProvider.adapter.name,
|
|
20321
20670
|
error: describeError(err)
|
|
@@ -20458,7 +20807,7 @@ var SnapshotService = class {
|
|
|
20458
20807
|
recommendedActions: uniqueStrings2(parsed.recommendedActions ?? []).slice(0, 4)
|
|
20459
20808
|
};
|
|
20460
20809
|
} catch (err) {
|
|
20461
|
-
|
|
20810
|
+
log24.warn("response.analysis-failed", {
|
|
20462
20811
|
provider: ctx.analysisProvider.adapter.name,
|
|
20463
20812
|
error: describeError(err)
|
|
20464
20813
|
});
|
|
@@ -20751,9 +21100,9 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
20751
21100
|
reserved = run.totalQueries;
|
|
20752
21101
|
reservation = { scope, period };
|
|
20753
21102
|
const rows = db.select().from(researchRunQueries).where(eq27(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
|
|
20754
|
-
const competitorRows = db.select(
|
|
21103
|
+
const competitorRows = db.select(competitorIdentityColumns).from(competitors).where(eq27(competitors.projectId, projectId)).orderBy(competitors.domain).all();
|
|
20755
21104
|
const competitorDomains = competitorRows.map((row) => row.domain);
|
|
20756
|
-
const competitorAliases = new Map(competitorRows.
|
|
21105
|
+
const competitorAliases = new Map(competitorRows.map((row) => [row.domain, competitorIdentityAliases(row)]).filter(([, aliases]) => aliases.length > 0));
|
|
20757
21106
|
const domains = effectiveDomains(project);
|
|
20758
21107
|
const brands = effectiveBrandNames(project);
|
|
20759
21108
|
const config = { ...provider.config, model: run.resolvedModel };
|
|
@@ -21086,7 +21435,7 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
21086
21435
|
// src/server.ts
|
|
21087
21436
|
var _require3 = createRequire3(import.meta.url);
|
|
21088
21437
|
var { version: PKG_VERSION2 } = _require3("../package.json");
|
|
21089
|
-
var
|
|
21438
|
+
var log25 = createLogger("Server");
|
|
21090
21439
|
var runtimeStartupByServer = /* @__PURE__ */ new WeakMap();
|
|
21091
21440
|
function waitForServerRuntimeStartup(app) {
|
|
21092
21441
|
const startup = runtimeStartupByServer.get(app);
|
|
@@ -21292,7 +21641,7 @@ function applyLegacyCredentials(rows, config) {
|
|
|
21292
21641
|
}
|
|
21293
21642
|
if (migratedGoogle > 0) {
|
|
21294
21643
|
saveConfigPatch({ google: config.google });
|
|
21295
|
-
|
|
21644
|
+
log25.info("credentials.migrated", { type: "google", count: migratedGoogle });
|
|
21296
21645
|
}
|
|
21297
21646
|
let migratedGa4 = 0;
|
|
21298
21647
|
for (const row of rows.ga4) {
|
|
@@ -21310,7 +21659,7 @@ function applyLegacyCredentials(rows, config) {
|
|
|
21310
21659
|
}
|
|
21311
21660
|
if (migratedGa4 > 0) {
|
|
21312
21661
|
saveConfigPatch({ ga4: config.ga4 });
|
|
21313
|
-
|
|
21662
|
+
log25.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
|
|
21314
21663
|
}
|
|
21315
21664
|
}
|
|
21316
21665
|
function isLoopbackBindHost(host) {
|
|
@@ -21547,11 +21896,11 @@ async function createServer(opts) {
|
|
|
21547
21896
|
applyLegacyCredentials(legacyRows, opts.config);
|
|
21548
21897
|
dropLegacyCredentialColumns(opts.db);
|
|
21549
21898
|
} catch (err) {
|
|
21550
|
-
|
|
21899
|
+
log25.warn("credentials.migration.failed", {
|
|
21551
21900
|
error: describeError(err)
|
|
21552
21901
|
});
|
|
21553
21902
|
}
|
|
21554
|
-
|
|
21903
|
+
log25.info("providers.configured", {
|
|
21555
21904
|
providers: Object.keys(providers).filter((k) => {
|
|
21556
21905
|
const p = providers[k];
|
|
21557
21906
|
return p?.apiKey || p?.baseUrl || p?.vertexProject;
|
|
@@ -21566,7 +21915,7 @@ async function createServer(opts) {
|
|
|
21566
21915
|
}
|
|
21567
21916
|
}
|
|
21568
21917
|
for (const name of providersWithUnsupportedBatch(providers, (provider) => adapterMap[provider])) {
|
|
21569
|
-
|
|
21918
|
+
log25.warn("provider.batch.unsupported", {
|
|
21570
21919
|
provider: name,
|
|
21571
21920
|
message: `providers.${name}.batch.enabled is true, but the ${name} adapter has no batch API; its sweeps run sync.`
|
|
21572
21921
|
});
|
|
@@ -22403,37 +22752,48 @@ async function createServer(opts) {
|
|
|
22403
22752
|
const sessionCookieSecure = Boolean(
|
|
22404
22753
|
opts.config.publicUrl?.startsWith("https://") || opts.config.apiUrl?.startsWith("https://")
|
|
22405
22754
|
);
|
|
22406
|
-
const
|
|
22407
|
-
|
|
22408
|
-
|
|
22409
|
-
|
|
22410
|
-
|
|
22411
|
-
|
|
22412
|
-
|
|
22413
|
-
|
|
22414
|
-
|
|
22415
|
-
|
|
22416
|
-
|
|
22755
|
+
const currentPasswordFingerprint = () => hashSessionToken(opts.config.dashboardPasswordHash ?? "");
|
|
22756
|
+
opts.db.delete(dashboardSessions).where(or3(
|
|
22757
|
+
eq29(dashboardSessions.apiKeyHash, ""),
|
|
22758
|
+
and23(
|
|
22759
|
+
isNotNull4(dashboardSessions.passwordFingerprint),
|
|
22760
|
+
ne3(dashboardSessions.passwordFingerprint, currentPasswordFingerprint())
|
|
22761
|
+
)
|
|
22762
|
+
)).run();
|
|
22763
|
+
const createSession = (key, passwordBased = false) => {
|
|
22764
|
+
const now = /* @__PURE__ */ new Date();
|
|
22765
|
+
opts.db.delete(dashboardSessions).where(lte2(dashboardSessions.expiresAt, now.toISOString())).run();
|
|
22417
22766
|
const sessionId = crypto29.randomBytes(32).toString("hex");
|
|
22418
|
-
|
|
22419
|
-
|
|
22420
|
-
|
|
22421
|
-
|
|
22767
|
+
opts.db.insert(dashboardSessions).values({
|
|
22768
|
+
tokenHash: hashSessionToken(sessionId),
|
|
22769
|
+
apiKeyId: key.id,
|
|
22770
|
+
apiKeyHash: key.keyHash,
|
|
22771
|
+
passwordFingerprint: passwordBased ? currentPasswordFingerprint() : null,
|
|
22772
|
+
createdAt: now.toISOString(),
|
|
22773
|
+
expiresAt: new Date(now.getTime() + SESSION_TTL_MS).toISOString()
|
|
22774
|
+
}).run();
|
|
22422
22775
|
return sessionId;
|
|
22423
22776
|
};
|
|
22424
22777
|
const resolveSessionApiKeyId = (sessionId) => {
|
|
22425
|
-
|
|
22426
|
-
const session =
|
|
22778
|
+
const tokenHash = hashSessionToken(sessionId);
|
|
22779
|
+
const session = opts.db.select({
|
|
22780
|
+
apiKeyId: dashboardSessions.apiKeyId,
|
|
22781
|
+
apiKeyHash: dashboardSessions.apiKeyHash,
|
|
22782
|
+
currentApiKeyHash: apiKeys.keyHash,
|
|
22783
|
+
passwordFingerprint: dashboardSessions.passwordFingerprint,
|
|
22784
|
+
expiresAt: dashboardSessions.expiresAt
|
|
22785
|
+
}).from(dashboardSessions).innerJoin(apiKeys, eq29(apiKeys.id, dashboardSessions.apiKeyId)).where(and23(eq29(dashboardSessions.tokenHash, tokenHash), isNull4(apiKeys.revokedAt))).get();
|
|
22427
22786
|
if (!session) return null;
|
|
22428
|
-
|
|
22429
|
-
|
|
22787
|
+
const expiresAt = Date.parse(session.expiresAt);
|
|
22788
|
+
if (!Number.isFinite(expiresAt) || expiresAt <= Date.now() || session.apiKeyHash !== session.currentApiKeyHash || session.passwordFingerprint !== null && session.passwordFingerprint !== currentPasswordFingerprint()) {
|
|
22789
|
+
opts.db.delete(dashboardSessions).where(eq29(dashboardSessions.tokenHash, tokenHash)).run();
|
|
22430
22790
|
return null;
|
|
22431
22791
|
}
|
|
22432
22792
|
return session.apiKeyId;
|
|
22433
22793
|
};
|
|
22434
22794
|
const clearSession = (sessionId) => {
|
|
22435
22795
|
if (sessionId) {
|
|
22436
|
-
|
|
22796
|
+
opts.db.delete(dashboardSessions).where(eq29(dashboardSessions.tokenHash, hashSessionToken(sessionId))).run();
|
|
22437
22797
|
}
|
|
22438
22798
|
};
|
|
22439
22799
|
const getDefaultApiKey = () => {
|
|
@@ -22443,7 +22803,7 @@ async function createServer(opts) {
|
|
|
22443
22803
|
const createPasswordSession = (reply) => {
|
|
22444
22804
|
const key = getDefaultApiKey();
|
|
22445
22805
|
if (!key || key.revokedAt) return false;
|
|
22446
|
-
const sessionId = createSession(key
|
|
22806
|
+
const sessionId = createSession(key, true);
|
|
22447
22807
|
reply.header(
|
|
22448
22808
|
"set-cookie",
|
|
22449
22809
|
serializeSessionCookie({
|
|
@@ -22563,7 +22923,7 @@ async function createServer(opts) {
|
|
|
22563
22923
|
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
22564
22924
|
}
|
|
22565
22925
|
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq29(apiKeys.id, key.id)).run();
|
|
22566
|
-
const sessionId = createSession(key
|
|
22926
|
+
const sessionId = createSession(key);
|
|
22567
22927
|
reply.header(
|
|
22568
22928
|
"set-cookie",
|
|
22569
22929
|
serializeSessionCookie({
|
|
@@ -22638,7 +22998,7 @@ async function createServer(opts) {
|
|
|
22638
22998
|
const sentimentPoller = createSentimentPoller(
|
|
22639
22999
|
() => sentimentWorker.tick(),
|
|
22640
23000
|
() => {
|
|
22641
|
-
|
|
23001
|
+
log25.error("sentiment.worker-failed", { reason: "Stored sentiment worker could not finish its tick." });
|
|
22642
23002
|
}
|
|
22643
23003
|
);
|
|
22644
23004
|
const pollSentiment = sentimentPoller.poll;
|
|
@@ -22646,6 +23006,110 @@ async function createServer(opts) {
|
|
|
22646
23006
|
runCoordinator.onSentimentCompleted = async () => {
|
|
22647
23007
|
pollSentiment();
|
|
22648
23008
|
};
|
|
23009
|
+
const answerFieldRecomputes = createAnswerFieldRecomputeQueue({ db: opts.db });
|
|
23010
|
+
const recomputeDebts = /* @__PURE__ */ new Map();
|
|
23011
|
+
let recomputeSequence = 0;
|
|
23012
|
+
const markAnswerFieldRecompute = (projectId, full) => {
|
|
23013
|
+
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();
|
|
23014
|
+
};
|
|
23015
|
+
const requestAnswerFieldRecompute = (projectId, projectName, scope, reason) => {
|
|
23016
|
+
const sequence = ++recomputeSequence;
|
|
23017
|
+
const debt = recomputeDebts.get(projectId) ?? { latest: 0, latestFull: 0 };
|
|
23018
|
+
debt.latest = sequence;
|
|
23019
|
+
if (scope.full) debt.latestFull = sequence;
|
|
23020
|
+
recomputeDebts.set(projectId, debt);
|
|
23021
|
+
try {
|
|
23022
|
+
markAnswerFieldRecompute(projectId, scope.full === true);
|
|
23023
|
+
} catch (err) {
|
|
23024
|
+
app.log.error({ err, projectId }, "could not mark the owed answer field recompute");
|
|
23025
|
+
}
|
|
23026
|
+
answerFieldRecomputes.request(projectId, scope).then(
|
|
23027
|
+
(pass) => {
|
|
23028
|
+
if (pass.full && debt.latestFull <= sequence) debt.latestFull = 0;
|
|
23029
|
+
try {
|
|
23030
|
+
if (debt.latest === sequence && debt.latestFull === 0) {
|
|
23031
|
+
opts.db.update(projects).set({ answerFieldsRecompute: null }).where(eq29(projects.id, projectId)).run();
|
|
23032
|
+
if (recomputeDebts.get(projectId) === debt) recomputeDebts.delete(projectId);
|
|
23033
|
+
} else if (debt.latestFull === 0) {
|
|
23034
|
+
opts.db.update(projects).set({ answerFieldsRecompute: "competitors" }).where(and23(eq29(projects.id, projectId), eq29(projects.answerFieldsRecompute, "full"))).run();
|
|
23035
|
+
}
|
|
23036
|
+
} catch (err) {
|
|
23037
|
+
app.log.error({ err, projectId }, "could not clear the owed answer field recompute");
|
|
23038
|
+
}
|
|
23039
|
+
app.log.info(
|
|
23040
|
+
{ projectId, projectName, reason, full: pass.full, ...pass.result },
|
|
23041
|
+
"identity changed: recomputed stored answer fields on historical snapshots"
|
|
23042
|
+
);
|
|
23043
|
+
},
|
|
23044
|
+
(err) => {
|
|
23045
|
+
app.log.error(
|
|
23046
|
+
{ err, projectId, projectName, reason },
|
|
23047
|
+
"identity-triggered answer field recompute failed"
|
|
23048
|
+
);
|
|
23049
|
+
}
|
|
23050
|
+
);
|
|
23051
|
+
};
|
|
23052
|
+
const resumeAnswerFieldRecomputes = () => {
|
|
23053
|
+
const owed = opts.db.select({ id: projects.id, name: projects.name, scope: projects.answerFieldsRecompute }).from(projects).where(isNotNull4(projects.answerFieldsRecompute)).all();
|
|
23054
|
+
for (const project of owed) {
|
|
23055
|
+
requestAnswerFieldRecompute(project.id, project.name, { full: project.scope === "full" }, "resume-after-restart");
|
|
23056
|
+
}
|
|
23057
|
+
};
|
|
23058
|
+
const recomputeCompetitorFields = (projectId, projectName) => {
|
|
23059
|
+
requestAnswerFieldRecompute(projectId, projectName, {}, "competitor-names");
|
|
23060
|
+
};
|
|
23061
|
+
const competitorAutoAliases = createCompetitorAutoAliasRunner({
|
|
23062
|
+
db: opts.db,
|
|
23063
|
+
readAnchors: extractStoredAnswerAnchors,
|
|
23064
|
+
onNamesChanged: recomputeCompetitorFields
|
|
23065
|
+
});
|
|
23066
|
+
const competitorAutoAliasReasons = /* @__PURE__ */ new Map();
|
|
23067
|
+
const competitorAutoAliasPasses = createProjectPassQueue(async (projectId) => {
|
|
23068
|
+
await yieldToEventLoop4();
|
|
23069
|
+
const reason = (competitorAutoAliasReasons.get(projectId) ?? []).join(",");
|
|
23070
|
+
competitorAutoAliasReasons.delete(projectId);
|
|
23071
|
+
const project = opts.db.select().from(projects).where(eq29(projects.id, projectId)).get();
|
|
23072
|
+
if (!project || project.competitorAutoAliases === "off") return;
|
|
23073
|
+
if (project.competitorAutoAliases === "apply") {
|
|
23074
|
+
await competitorAutoAliases.request(projectId, reason).catch(() => void 0);
|
|
23075
|
+
return;
|
|
23076
|
+
}
|
|
23077
|
+
const detection = await previewCompetitorAutoAliases(opts.db, project, { readAnchors: extractStoredAnswerAnchors });
|
|
23078
|
+
const wouldAdd = detection.competitors.flatMap((competitor) => competitor.added.map((name) => `${competitor.domain}: ${name}`));
|
|
23079
|
+
const wouldRemove = detection.competitors.flatMap((competitor) => competitor.removed.map((entry) => `${competitor.domain}: ${entry.name}`));
|
|
23080
|
+
app.log.info(
|
|
23081
|
+
{
|
|
23082
|
+
projectId,
|
|
23083
|
+
projectName: project.name,
|
|
23084
|
+
reason,
|
|
23085
|
+
mode: "preview",
|
|
23086
|
+
runs: detection.scan.runs,
|
|
23087
|
+
snapshots: detection.scan.snapshots,
|
|
23088
|
+
wouldAdd: wouldAdd.slice(0, 20),
|
|
23089
|
+
wouldAddCount: wouldAdd.length,
|
|
23090
|
+
wouldRemove: wouldRemove.slice(0, 20),
|
|
23091
|
+
wouldRemoveCount: wouldRemove.length
|
|
23092
|
+
},
|
|
23093
|
+
"competitor auto aliases (preview): stored nothing; set the project to apply, or run detect --apply, to store them"
|
|
23094
|
+
);
|
|
23095
|
+
});
|
|
23096
|
+
const scheduleCompetitorAutoAliases = (projectId, reason) => {
|
|
23097
|
+
competitorAutoAliasReasons.set(projectId, [...competitorAutoAliasReasons.get(projectId) ?? [], reason]);
|
|
23098
|
+
competitorAutoAliasPasses.request(projectId).catch((err) => {
|
|
23099
|
+
app.log.error({ err, projectId, reason }, "competitor auto alias pass failed");
|
|
23100
|
+
});
|
|
23101
|
+
};
|
|
23102
|
+
runCoordinator.onAnswersRecorded = (runId, projectId) => {
|
|
23103
|
+
scheduleCompetitorAutoAliases(projectId, `run:${runId}`);
|
|
23104
|
+
};
|
|
23105
|
+
app.addHook("onClose", async () => {
|
|
23106
|
+
await competitorAutoAliasPasses.settled();
|
|
23107
|
+
await competitorAutoAliases.settled();
|
|
23108
|
+
await answerFieldRecomputes.settled();
|
|
23109
|
+
});
|
|
23110
|
+
app.addHook("onReady", async () => {
|
|
23111
|
+
resumeAnswerFieldRecomputes();
|
|
23112
|
+
});
|
|
22649
23113
|
app.addHook("onReady", async () => {
|
|
22650
23114
|
sentimentTimer = setInterval(pollSentiment, 5e3);
|
|
22651
23115
|
sentimentTimer.unref();
|
|
@@ -23207,37 +23671,14 @@ async function createServer(opts) {
|
|
|
23207
23671
|
}
|
|
23208
23672
|
},
|
|
23209
23673
|
onAliasesChanged: (projectId, projectName) => {
|
|
23210
|
-
|
|
23211
|
-
try {
|
|
23212
|
-
const result2 = backfillProjectAnswerMentions(opts.db, projectId);
|
|
23213
|
-
app.log.info(
|
|
23214
|
-
{ projectId, projectName, ...result2 },
|
|
23215
|
-
"aliases changed \u2014 recomputed mention fields on historical snapshots"
|
|
23216
|
-
);
|
|
23217
|
-
} catch (err) {
|
|
23218
|
-
app.log.error(
|
|
23219
|
-
{ err, projectId, projectName },
|
|
23220
|
-
"alias-triggered backfill failed"
|
|
23221
|
-
);
|
|
23222
|
-
}
|
|
23223
|
-
});
|
|
23674
|
+
requestAnswerFieldRecompute(projectId, projectName, { full: true }, "project-aliases");
|
|
23224
23675
|
},
|
|
23225
|
-
onCompetitorAliasesChanged:
|
|
23226
|
-
|
|
23227
|
-
|
|
23228
|
-
const result2 = backfillProjectAnswerMentions(opts.db, projectId, { competitorFieldsOnly: true });
|
|
23229
|
-
app.log.info(
|
|
23230
|
-
{ projectId, projectName, ...result2 },
|
|
23231
|
-
"competitor aliases changed: recomputed competitor fields on historical snapshots"
|
|
23232
|
-
);
|
|
23233
|
-
} catch (err) {
|
|
23234
|
-
app.log.error(
|
|
23235
|
-
{ err, projectId, projectName },
|
|
23236
|
-
"competitor-alias-triggered backfill failed"
|
|
23237
|
-
);
|
|
23238
|
-
}
|
|
23239
|
-
});
|
|
23676
|
+
onCompetitorAliasesChanged: recomputeCompetitorFields,
|
|
23677
|
+
onCompetitorAutoAliasRescan: (projectId) => {
|
|
23678
|
+
scheduleCompetitorAutoAliases(projectId, "rescan");
|
|
23240
23679
|
},
|
|
23680
|
+
competitorAnswerAnchors: extractStoredAnswerAnchors,
|
|
23681
|
+
runCompetitorAutoAliasPass: (projectId) => competitorAutoAliases.request(projectId, "api"),
|
|
23241
23682
|
operatorApiKeyIds,
|
|
23242
23683
|
listOperationalLogs: (query) => operationalLogs.list(query),
|
|
23243
23684
|
getTelemetryStatus,
|
|
@@ -23468,11 +23909,18 @@ async function createServer(opts) {
|
|
|
23468
23909
|
resourcePaths: mcpTransportPaths().map(
|
|
23469
23910
|
(suffix) => `${basePath ?? "/"}api/v1${suffix}`.replace("//", "/")
|
|
23470
23911
|
),
|
|
23471
|
-
resolveUser: (request) => {
|
|
23912
|
+
resolveUser: (request, reply) => {
|
|
23472
23913
|
const cookies = parseCookieHeader(request.headers.cookie);
|
|
23473
23914
|
const token = cookies[USER_SESSION_COOKIE_NAME];
|
|
23474
23915
|
if (!token) return null;
|
|
23475
23916
|
const resolved = resolveUserSession(opts.db, token);
|
|
23917
|
+
if (resolved?.renewedExpiresAt) {
|
|
23918
|
+
reply.header("set-cookie", serializeUserSessionCookie({
|
|
23919
|
+
value: token,
|
|
23920
|
+
path: sessionCookiePath,
|
|
23921
|
+
secure: cookieIsSecure(request, sessionCookieSecure ? true : void 0)
|
|
23922
|
+
}));
|
|
23923
|
+
}
|
|
23476
23924
|
return resolved ? { id: resolved.user.id, name: resolved.user.name } : null;
|
|
23477
23925
|
},
|
|
23478
23926
|
credentials: credentialChecker,
|
|
@@ -23614,6 +24062,7 @@ export {
|
|
|
23614
24062
|
reparseStoredResult as reparseStoredResult3,
|
|
23615
24063
|
reparseStoredResult4,
|
|
23616
24064
|
reparseStoredResult5,
|
|
24065
|
+
resolveStoredApiResponse,
|
|
23617
24066
|
getCloudflareTrafficConnectionBySourceId,
|
|
23618
24067
|
setGoogleAuthConfig,
|
|
23619
24068
|
formatAuditFactorScore,
|