@ainyc/canonry 4.55.1 → 4.56.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/SKILL.md +2 -0
- package/assets/agent-workspace/skills/canonry/SKILL.md +2 -0
- package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +55 -7
- package/assets/assets/{BacklinksPage-DVmaM864.js → BacklinksPage-yWx_BBLG.js} +1 -1
- package/assets/assets/ChartPrimitives-gqioJ07n.js +1 -0
- package/assets/assets/ProjectPage-D1tnXJ2L.js +6 -0
- package/assets/assets/{RunRow-BRqiLxj2.js → RunRow-DEr_yQLw.js} +1 -1
- package/assets/assets/{RunsPage-UxZ93-cg.js → RunsPage-DMrl5Fhn.js} +1 -1
- package/assets/assets/{SettingsPage-Cr5_EGbk.js → SettingsPage-Dp0TXf34.js} +1 -1
- package/assets/assets/{TrafficPage-CUC_lfTe.js → TrafficPage-POy4iHHt.js} +1 -1
- package/assets/assets/TrafficSourceDetailPage-BewjZ53n.js +1 -0
- package/assets/assets/{extract-error-message-DD5MibWI.js → extract-error-message-C0GGpK4T.js} +1 -1
- package/assets/assets/{index-nnF1LnyK.js → index-D0A-UvNH.js} +79 -79
- package/assets/assets/index-_jdnW4nh.css +1 -0
- package/assets/assets/{server-traffic-DjRISEZ-.js → server-traffic-B5rtrB-q.js} +1 -1
- package/assets/assets/{trash-2-CJ5M--Le.js → trash-2-CUczQ2Yl.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-UTM3FPAJ.js → chunk-4KWPOVIT.js} +181 -3
- package/dist/{chunk-ZY3EDW3S.js → chunk-6X5TF73A.js} +49 -3
- package/dist/{chunk-2OI7HFAB.js → chunk-I2LAM5IM.js} +309 -222
- package/dist/{chunk-OFY3Z2F7.js → chunk-WFVUZVJD.js} +368 -361
- package/dist/cli.js +66 -11
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-NKAEHHJ5.js → intelligence-service-NY3MAVPB.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +10 -10
- package/assets/assets/ChartPrimitives-9Kx3gzQL.js +0 -1
- package/assets/assets/ProjectPage-DtL3LFne.js +0 -6
- package/assets/assets/TrafficSourceDetailPage-DARPL2TU.js +0 -1
- package/assets/assets/index-Bm3JQsW0.css +0 -1
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
loadConfig,
|
|
7
7
|
loadConfigRaw,
|
|
8
8
|
saveConfigPatch
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-6X5TF73A.js";
|
|
10
10
|
import {
|
|
11
11
|
DEFAULT_RUN_HISTORY_LIMIT,
|
|
12
12
|
IntelligenceService,
|
|
@@ -45,14 +45,17 @@ import {
|
|
|
45
45
|
categorizeQueryByIntent,
|
|
46
46
|
ccReleaseSyncs,
|
|
47
47
|
competitors,
|
|
48
|
+
computeCompetitorOverlap,
|
|
48
49
|
contentTargetDismissals,
|
|
49
50
|
crawlerEventsHourly,
|
|
50
51
|
createClient,
|
|
51
52
|
createLogger,
|
|
53
|
+
determineCitationState,
|
|
52
54
|
discoveryProbes,
|
|
53
55
|
discoverySessions,
|
|
54
56
|
dropLegacyCredentialColumns,
|
|
55
57
|
extractLegacyCredentials,
|
|
58
|
+
extractRecommendedCompetitors,
|
|
56
59
|
filterTrackedSnapshots,
|
|
57
60
|
gaAiReferrals,
|
|
58
61
|
gaSocialReferrals,
|
|
@@ -84,7 +87,7 @@ import {
|
|
|
84
87
|
smoothedRunDelta,
|
|
85
88
|
trafficSources,
|
|
86
89
|
usageCounters
|
|
87
|
-
} from "./chunk-
|
|
90
|
+
} from "./chunk-4KWPOVIT.js";
|
|
88
91
|
import {
|
|
89
92
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
90
93
|
AGENT_PROVIDER_IDS,
|
|
@@ -144,7 +147,6 @@ import {
|
|
|
144
147
|
bingSitesResponseDtoSchema,
|
|
145
148
|
bingStatusDtoSchema,
|
|
146
149
|
bingUrlInspectionDtoSchema,
|
|
147
|
-
brandKeyFromText,
|
|
148
150
|
brandLabelFromDomain,
|
|
149
151
|
buildRunErrorFromMessages,
|
|
150
152
|
categorizeSource,
|
|
@@ -265,6 +267,7 @@ import {
|
|
|
265
267
|
trafficConnectVercelRequestSchema,
|
|
266
268
|
trafficConnectWordpressRequestSchema,
|
|
267
269
|
trafficEventsResponseSchema,
|
|
270
|
+
trafficResetRequestSchema,
|
|
268
271
|
trafficSourceDetailDtoSchema,
|
|
269
272
|
trafficSourceDtoSchema,
|
|
270
273
|
trafficSourceListResponseSchema,
|
|
@@ -286,7 +289,7 @@ import {
|
|
|
286
289
|
wordpressSchemaDeployResultDtoSchema,
|
|
287
290
|
wordpressSchemaStatusResultDtoSchema,
|
|
288
291
|
wordpressStatusDtoSchema
|
|
289
|
-
} from "./chunk-
|
|
292
|
+
} from "./chunk-WFVUZVJD.js";
|
|
290
293
|
|
|
291
294
|
// src/telemetry.ts
|
|
292
295
|
import crypto from "crypto";
|
|
@@ -1657,7 +1660,9 @@ async function runRoutes(app, opts) {
|
|
|
1657
1660
|
const project = resolveProject(app.db, request.params.name);
|
|
1658
1661
|
const parsedLimit = parseInt(request.query.limit ?? "", 10);
|
|
1659
1662
|
const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? void 0 : parsedLimit;
|
|
1660
|
-
const
|
|
1663
|
+
const kind = parseListKind(request.query.kind);
|
|
1664
|
+
const where = kind ? and2(eq7(runs.projectId, project.id), eq7(runs.kind, kind)) : eq7(runs.projectId, project.id);
|
|
1665
|
+
const rows = limit == null ? app.db.select().from(runs).where(where).orderBy(asc(runs.createdAt)).all() : app.db.select().from(runs).where(where).orderBy(desc(runs.createdAt)).limit(limit).all().reverse();
|
|
1661
1666
|
return reply.send(rows.map(formatRun));
|
|
1662
1667
|
});
|
|
1663
1668
|
app.get("/projects/:name/runs/latest", async (request, reply) => {
|
|
@@ -9118,7 +9123,7 @@ var routeCatalog = [
|
|
|
9118
9123
|
path: "/api/v1/projects/{name}/runs",
|
|
9119
9124
|
summary: "List project runs",
|
|
9120
9125
|
tags: ["runs"],
|
|
9121
|
-
parameters: [nameParameter, limitQueryParameter],
|
|
9126
|
+
parameters: [nameParameter, limitQueryParameter, runsListKindQueryParameter],
|
|
9122
9127
|
responses: {
|
|
9123
9128
|
200: jsonArrayResponse("Runs returned.", "RunDto")
|
|
9124
9129
|
}
|
|
@@ -11498,6 +11503,36 @@ var routeCatalog = [
|
|
|
11498
11503
|
404: errorResponse("Project or traffic source not found.")
|
|
11499
11504
|
}
|
|
11500
11505
|
},
|
|
11506
|
+
{
|
|
11507
|
+
method: "post",
|
|
11508
|
+
path: "/api/v1/projects/{name}/traffic/sources/{id}/reset",
|
|
11509
|
+
summary: "Advance lastSyncedAt to NOW and clear the error state",
|
|
11510
|
+
description: "Operator recovery: advances `lastSyncedAt` to NOW, sets `status` back to `connected`, and clears `last_error`. Accepts any non-archived source \u2014 the `lastSyncedAt` advance determines the next sync window for time-windowed sources (Vercel, Cloud Run) and is informational for cursor-based sources (WordPress, where `last_cursor` governs the next drain and is preserved). Common trigger: an idle Vercel/Cloud Run source whose `lastSyncedAt` has aged past the upstream retention window (`request-logs` ~14d, Cloud Logging 30d) and now throws on every sync. Any pre-existing rollup history stays in place; the skipped history is the explicit trade-off \u2014 run `traffic backfill` separately to recover any of it. `advanceToNow: true` is required (no implicit reset). Archived sources are rejected with 400 \u2014 re-connect them via the appropriate `traffic/connect/*` endpoint instead.",
|
|
11511
|
+
tags: ["traffic"],
|
|
11512
|
+
parameters: [
|
|
11513
|
+
nameParameter,
|
|
11514
|
+
{ name: "id", in: "path", required: true, description: "Traffic source ID.", schema: stringSchema }
|
|
11515
|
+
],
|
|
11516
|
+
requestBody: {
|
|
11517
|
+
required: true,
|
|
11518
|
+
content: {
|
|
11519
|
+
"application/json": {
|
|
11520
|
+
schema: {
|
|
11521
|
+
type: "object",
|
|
11522
|
+
required: ["advanceToNow"],
|
|
11523
|
+
properties: {
|
|
11524
|
+
advanceToNow: { type: "boolean", enum: [true], description: "Must be `true` \u2014 explicit gate against accidental resets." }
|
|
11525
|
+
}
|
|
11526
|
+
}
|
|
11527
|
+
}
|
|
11528
|
+
}
|
|
11529
|
+
},
|
|
11530
|
+
responses: {
|
|
11531
|
+
200: jsonResponse("Source reset; lastSyncedAt advanced to NOW.", "TrafficSourceDetailDto"),
|
|
11532
|
+
400: errorResponse("Missing or invalid `advanceToNow` flag, or the source is archived."),
|
|
11533
|
+
404: errorResponse("Project or traffic source not found.")
|
|
11534
|
+
}
|
|
11535
|
+
},
|
|
11501
11536
|
{
|
|
11502
11537
|
method: "get",
|
|
11503
11538
|
path: "/api/v1/projects/{name}/traffic/sources",
|
|
@@ -21586,14 +21621,34 @@ var VERCEL_REQUEST_LOGS_URL = "https://vercel.com/api/logs/request-logs";
|
|
|
21586
21621
|
var DEFAULT_ENVIRONMENT = "production";
|
|
21587
21622
|
var DEFAULT_MAX_PAGES3 = 1;
|
|
21588
21623
|
var DEFAULT_TIMEOUT_MS3 = 3e4;
|
|
21624
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
21625
|
+
var DEFAULT_INITIAL_RETRY_DELAY_MS = 1e3;
|
|
21589
21626
|
var VercelLogsApiError = class extends Error {
|
|
21590
|
-
constructor(message, status, body) {
|
|
21627
|
+
constructor(message, status, body, retryAfterSeconds) {
|
|
21591
21628
|
super(message);
|
|
21592
21629
|
this.status = status;
|
|
21593
21630
|
this.body = body;
|
|
21631
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
21594
21632
|
this.name = "VercelLogsApiError";
|
|
21595
21633
|
}
|
|
21596
21634
|
};
|
|
21635
|
+
function parseRetryAfter2(headerValue) {
|
|
21636
|
+
if (!headerValue) return void 0;
|
|
21637
|
+
const trimmed = headerValue.trim();
|
|
21638
|
+
const asNum = Number(trimmed);
|
|
21639
|
+
if (Number.isFinite(asNum) && asNum >= 0) return asNum;
|
|
21640
|
+
const asDate = Date.parse(trimmed);
|
|
21641
|
+
if (!Number.isNaN(asDate)) {
|
|
21642
|
+
return Math.max(0, (asDate - Date.now()) / 1e3);
|
|
21643
|
+
}
|
|
21644
|
+
return void 0;
|
|
21645
|
+
}
|
|
21646
|
+
function isRetryableVercelError(error) {
|
|
21647
|
+
if (error instanceof VercelLogsApiError) {
|
|
21648
|
+
return error.status === 429 || error.status >= 500;
|
|
21649
|
+
}
|
|
21650
|
+
return true;
|
|
21651
|
+
}
|
|
21597
21652
|
function trimRequired2(name, value) {
|
|
21598
21653
|
const trimmed = value.trim();
|
|
21599
21654
|
if (!trimmed) {
|
|
@@ -21620,6 +21675,24 @@ async function readErrorBody3(response) {
|
|
|
21620
21675
|
if (!text) return void 0;
|
|
21621
21676
|
return text.length <= 500 ? text : `${text.slice(0, 500)}... [truncated]`;
|
|
21622
21677
|
}
|
|
21678
|
+
async function withVercelRetry(attempt, maxRetries, initialDelayMs) {
|
|
21679
|
+
let lastError;
|
|
21680
|
+
for (let attemptNumber = 0; attemptNumber <= maxRetries; attemptNumber += 1) {
|
|
21681
|
+
try {
|
|
21682
|
+
return await attempt();
|
|
21683
|
+
} catch (error) {
|
|
21684
|
+
lastError = error;
|
|
21685
|
+
if (attemptNumber >= maxRetries || !isRetryableVercelError(error)) throw error;
|
|
21686
|
+
const retryAfterSeconds = error instanceof VercelLogsApiError ? error.retryAfterSeconds : void 0;
|
|
21687
|
+
const computedDelayMs = initialDelayMs * Math.pow(2, attemptNumber);
|
|
21688
|
+
const delayMs = retryAfterSeconds !== void 0 ? Math.max(0, retryAfterSeconds * 1e3) : computedDelayMs;
|
|
21689
|
+
if (delayMs > 0) {
|
|
21690
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
21691
|
+
}
|
|
21692
|
+
}
|
|
21693
|
+
}
|
|
21694
|
+
throw lastError;
|
|
21695
|
+
}
|
|
21623
21696
|
async function listVercelTrafficEvents(options) {
|
|
21624
21697
|
const token = trimRequired2("token", options.token);
|
|
21625
21698
|
const projectId = trimRequired2("projectId", options.projectId);
|
|
@@ -21629,6 +21702,8 @@ async function listVercelTrafficEvents(options) {
|
|
|
21629
21702
|
const endDate = toEpochMs("endDate", options.endDate);
|
|
21630
21703
|
const maxPages = normalizeMaxPages3(options.maxPages);
|
|
21631
21704
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
|
|
21705
|
+
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
21706
|
+
const initialRetryDelayMs = options.initialRetryDelayMs ?? DEFAULT_INITIAL_RETRY_DELAY_MS;
|
|
21632
21707
|
let rawEntryCount = 0;
|
|
21633
21708
|
let skippedEntryCount = 0;
|
|
21634
21709
|
let hasMore = false;
|
|
@@ -21642,23 +21717,27 @@ async function listVercelTrafficEvents(options) {
|
|
|
21642
21717
|
url.searchParams.set("startDate", startDate);
|
|
21643
21718
|
url.searchParams.set("endDate", endDate);
|
|
21644
21719
|
url.searchParams.set("environment", environment);
|
|
21645
|
-
const
|
|
21646
|
-
|
|
21647
|
-
|
|
21648
|
-
|
|
21649
|
-
|
|
21650
|
-
|
|
21651
|
-
|
|
21652
|
-
|
|
21653
|
-
|
|
21654
|
-
|
|
21655
|
-
|
|
21656
|
-
|
|
21657
|
-
|
|
21658
|
-
|
|
21659
|
-
|
|
21660
|
-
|
|
21661
|
-
|
|
21720
|
+
const body = await withVercelRetry(async () => {
|
|
21721
|
+
const response = await fetch(url, {
|
|
21722
|
+
method: "GET",
|
|
21723
|
+
headers: {
|
|
21724
|
+
Authorization: `Bearer ${token}`,
|
|
21725
|
+
Accept: "application/json"
|
|
21726
|
+
},
|
|
21727
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
21728
|
+
});
|
|
21729
|
+
if (!response.ok) {
|
|
21730
|
+
const errorBody = await readErrorBody3(response);
|
|
21731
|
+
const retryAfterSeconds = parseRetryAfter2(response.headers.get("retry-after"));
|
|
21732
|
+
throw new VercelLogsApiError(
|
|
21733
|
+
`Vercel request-logs endpoint returned HTTP ${response.status}`,
|
|
21734
|
+
response.status,
|
|
21735
|
+
errorBody,
|
|
21736
|
+
retryAfterSeconds
|
|
21737
|
+
);
|
|
21738
|
+
}
|
|
21739
|
+
return await response.json();
|
|
21740
|
+
}, maxRetries, initialRetryDelayMs);
|
|
21662
21741
|
const rows = body.rows ?? [];
|
|
21663
21742
|
rawEntryCount += rows.length;
|
|
21664
21743
|
for (const row of rows) {
|
|
@@ -21682,19 +21761,67 @@ async function listVercelTrafficEvents(options) {
|
|
|
21682
21761
|
}
|
|
21683
21762
|
|
|
21684
21763
|
// ../integration-vercel/src/drain.ts
|
|
21685
|
-
var MIN_SUB_WINDOW_MS =
|
|
21764
|
+
var MIN_SUB_WINDOW_MS = 1e3;
|
|
21765
|
+
var FLOOR_SLICE_MAX_PAGES = 1e3;
|
|
21766
|
+
var FLOOR_CONGESTION_PROBE_INTERVAL = 60;
|
|
21767
|
+
var RETENTION_PROBE_WINDOW_MS = 6e4;
|
|
21768
|
+
var RETENTION_BOUNDARY_TOLERANCE_MS = 60 * 6e4;
|
|
21686
21769
|
function toMs(value) {
|
|
21687
21770
|
return typeof value === "number" ? value : value.getTime();
|
|
21688
21771
|
}
|
|
21772
|
+
function isRetentionError(error) {
|
|
21773
|
+
return error instanceof VercelLogsApiError && error.status === 400 && (error.body ?? "").includes("ExceedsBillingLimitError");
|
|
21774
|
+
}
|
|
21775
|
+
async function isServable(options, windowStartMs, windowEndMs) {
|
|
21776
|
+
try {
|
|
21777
|
+
await options.pull({
|
|
21778
|
+
token: options.token,
|
|
21779
|
+
projectId: options.projectId,
|
|
21780
|
+
teamId: options.teamId,
|
|
21781
|
+
environment: options.environment,
|
|
21782
|
+
startDate: windowStartMs,
|
|
21783
|
+
endDate: windowEndMs,
|
|
21784
|
+
maxPages: 1
|
|
21785
|
+
});
|
|
21786
|
+
return true;
|
|
21787
|
+
} catch (error) {
|
|
21788
|
+
if (isRetentionError(error)) return false;
|
|
21789
|
+
throw error;
|
|
21790
|
+
}
|
|
21791
|
+
}
|
|
21792
|
+
async function resolveRetainedStart(options, unservableStartMs, endMs) {
|
|
21793
|
+
const tailStartMs = Math.max(unservableStartMs, endMs - RETENTION_PROBE_WINDOW_MS);
|
|
21794
|
+
if (!await isServable(options, tailStartMs, endMs)) {
|
|
21795
|
+
return endMs;
|
|
21796
|
+
}
|
|
21797
|
+
let lo = unservableStartMs;
|
|
21798
|
+
let hi = tailStartMs;
|
|
21799
|
+
while (hi - lo > RETENTION_BOUNDARY_TOLERANCE_MS) {
|
|
21800
|
+
const mid = lo + Math.floor((hi - lo) / 2);
|
|
21801
|
+
if (await isServable(options, mid, endMs)) {
|
|
21802
|
+
hi = mid;
|
|
21803
|
+
} else {
|
|
21804
|
+
lo = mid;
|
|
21805
|
+
}
|
|
21806
|
+
}
|
|
21807
|
+
return hi;
|
|
21808
|
+
}
|
|
21689
21809
|
async function drainVercelTrafficEvents(options) {
|
|
21690
21810
|
const startMs = toMs(options.startDate);
|
|
21691
21811
|
const endMs = toMs(options.endDate);
|
|
21692
21812
|
const events = [];
|
|
21693
21813
|
const seenEventIds = /* @__PURE__ */ new Set();
|
|
21694
|
-
if (endMs <= startMs)
|
|
21814
|
+
if (endMs <= startMs) {
|
|
21815
|
+
return { events, subWindowCount: 0, effectiveStartMs: startMs, retentionClamped: false };
|
|
21816
|
+
}
|
|
21695
21817
|
let cursorMs = startMs;
|
|
21696
21818
|
let spanMs = endMs - startMs;
|
|
21697
21819
|
let subWindowCount = 0;
|
|
21820
|
+
let effectiveStartMs = startMs;
|
|
21821
|
+
let retentionClamped = false;
|
|
21822
|
+
let retentionResolved = false;
|
|
21823
|
+
let floorSpanProbeCountdown = 0;
|
|
21824
|
+
let floorPageBudgetCountdown = 0;
|
|
21698
21825
|
while (cursorMs < endMs) {
|
|
21699
21826
|
if (subWindowCount >= options.maxSubWindows) {
|
|
21700
21827
|
throw new Error(
|
|
@@ -21702,25 +21829,63 @@ async function drainVercelTrafficEvents(options) {
|
|
|
21702
21829
|
);
|
|
21703
21830
|
}
|
|
21704
21831
|
const subEndMs = Math.min(cursorMs + spanMs, endMs);
|
|
21705
|
-
const
|
|
21706
|
-
|
|
21707
|
-
|
|
21708
|
-
|
|
21709
|
-
|
|
21710
|
-
|
|
21711
|
-
|
|
21712
|
-
|
|
21713
|
-
|
|
21832
|
+
const subSpanMs = subEndMs - cursorMs;
|
|
21833
|
+
const useFloorPageBudget = subSpanMs <= MIN_SUB_WINDOW_MS && floorPageBudgetCountdown > 0;
|
|
21834
|
+
const pageBudget = useFloorPageBudget ? FLOOR_SLICE_MAX_PAGES : options.pagesPerSubWindow;
|
|
21835
|
+
let page;
|
|
21836
|
+
try {
|
|
21837
|
+
page = await options.pull({
|
|
21838
|
+
token: options.token,
|
|
21839
|
+
projectId: options.projectId,
|
|
21840
|
+
teamId: options.teamId,
|
|
21841
|
+
environment: options.environment,
|
|
21842
|
+
startDate: cursorMs,
|
|
21843
|
+
endDate: subEndMs,
|
|
21844
|
+
maxPages: pageBudget
|
|
21845
|
+
});
|
|
21846
|
+
} catch (error) {
|
|
21847
|
+
if (isRetentionError(error) && !retentionResolved) {
|
|
21848
|
+
retentionResolved = true;
|
|
21849
|
+
const retainedStartMs = await resolveRetainedStart(options, cursorMs, endMs);
|
|
21850
|
+
retentionClamped = retainedStartMs > cursorMs;
|
|
21851
|
+
cursorMs = retainedStartMs;
|
|
21852
|
+
effectiveStartMs = retainedStartMs;
|
|
21853
|
+
spanMs = Math.max(endMs - cursorMs, MIN_SUB_WINDOW_MS);
|
|
21854
|
+
continue;
|
|
21855
|
+
}
|
|
21856
|
+
throw error;
|
|
21857
|
+
}
|
|
21714
21858
|
subWindowCount += 1;
|
|
21715
21859
|
if (page.hasMore) {
|
|
21716
|
-
|
|
21717
|
-
|
|
21860
|
+
if (subSpanMs > MIN_SUB_WINDOW_MS) {
|
|
21861
|
+
spanMs = Math.max(Math.floor(subSpanMs / 2), MIN_SUB_WINDOW_MS);
|
|
21862
|
+
if (spanMs === MIN_SUB_WINDOW_MS) {
|
|
21863
|
+
floorSpanProbeCountdown = FLOOR_CONGESTION_PROBE_INTERVAL;
|
|
21864
|
+
}
|
|
21865
|
+
continue;
|
|
21866
|
+
}
|
|
21867
|
+
if (pageBudget >= FLOOR_SLICE_MAX_PAGES) {
|
|
21718
21868
|
throw new Error(
|
|
21719
|
-
`Vercel
|
|
21869
|
+
`Vercel ${MIN_SUB_WINDOW_MS / 1e3}-second slice holds more than ${FLOOR_SLICE_MAX_PAGES} pages and cannot be drained further`
|
|
21870
|
+
);
|
|
21871
|
+
}
|
|
21872
|
+
page = await options.pull({
|
|
21873
|
+
token: options.token,
|
|
21874
|
+
projectId: options.projectId,
|
|
21875
|
+
teamId: options.teamId,
|
|
21876
|
+
environment: options.environment,
|
|
21877
|
+
startDate: cursorMs,
|
|
21878
|
+
endDate: subEndMs,
|
|
21879
|
+
maxPages: FLOOR_SLICE_MAX_PAGES
|
|
21880
|
+
});
|
|
21881
|
+
subWindowCount += 1;
|
|
21882
|
+
floorSpanProbeCountdown = FLOOR_CONGESTION_PROBE_INTERVAL;
|
|
21883
|
+
floorPageBudgetCountdown = FLOOR_CONGESTION_PROBE_INTERVAL;
|
|
21884
|
+
if (page.hasMore) {
|
|
21885
|
+
throw new Error(
|
|
21886
|
+
`Vercel ${MIN_SUB_WINDOW_MS / 1e3}-second slice holds more than ${FLOOR_SLICE_MAX_PAGES} pages and cannot be drained further`
|
|
21720
21887
|
);
|
|
21721
21888
|
}
|
|
21722
|
-
spanMs = Math.max(Math.floor(subSpanMs / 2), MIN_SUB_WINDOW_MS);
|
|
21723
|
-
continue;
|
|
21724
21889
|
}
|
|
21725
21890
|
for (const event of page.events) {
|
|
21726
21891
|
if (!seenEventIds.has(event.eventId)) {
|
|
@@ -21731,10 +21896,19 @@ async function drainVercelTrafficEvents(options) {
|
|
|
21731
21896
|
cursorMs = subEndMs;
|
|
21732
21897
|
const remainingMs = endMs - cursorMs;
|
|
21733
21898
|
if (remainingMs > 0) {
|
|
21734
|
-
|
|
21899
|
+
if (spanMs === MIN_SUB_WINDOW_MS && floorSpanProbeCountdown > 0) {
|
|
21900
|
+
floorSpanProbeCountdown -= 1;
|
|
21901
|
+
if (floorPageBudgetCountdown > 0) floorPageBudgetCountdown -= 1;
|
|
21902
|
+
spanMs = Math.min(MIN_SUB_WINDOW_MS, remainingMs);
|
|
21903
|
+
} else {
|
|
21904
|
+
spanMs = Math.min(spanMs * 2, remainingMs);
|
|
21905
|
+
if (spanMs > MIN_SUB_WINDOW_MS) {
|
|
21906
|
+
floorPageBudgetCountdown = 0;
|
|
21907
|
+
}
|
|
21908
|
+
}
|
|
21735
21909
|
}
|
|
21736
21910
|
}
|
|
21737
|
-
return { events, subWindowCount };
|
|
21911
|
+
return { events, subWindowCount, effectiveStartMs, retentionClamped };
|
|
21738
21912
|
}
|
|
21739
21913
|
|
|
21740
21914
|
// ../api-routes/src/traffic.ts
|
|
@@ -21746,6 +21920,7 @@ var DEFAULT_WP_PAGE_SIZE = 500;
|
|
|
21746
21920
|
var DEFAULT_WP_MAX_PAGES = 20;
|
|
21747
21921
|
var DEFAULT_VERCEL_MAX_PAGES = 50;
|
|
21748
21922
|
var VERCEL_MAX_SUB_WINDOWS = 5e3;
|
|
21923
|
+
var VERCEL_BACKFILL_CHUNK_MS = 60 * 6e4;
|
|
21749
21924
|
var MAX_TRACKED_EVENT_IDS = 1e3;
|
|
21750
21925
|
var DEFAULT_BACKFILL_DAYS = 30;
|
|
21751
21926
|
var MAX_BACKFILL_DAYS = 90;
|
|
@@ -21781,6 +21956,11 @@ async function defaultResolveAccessToken(record) {
|
|
|
21781
21956
|
"OAuth-mode Cloud Run sync is not yet supported in v1. Provide a service-account key file."
|
|
21782
21957
|
);
|
|
21783
21958
|
}
|
|
21959
|
+
function vercelRetentionClampError(requestedStartMs, effectiveStartMs) {
|
|
21960
|
+
return new Error(
|
|
21961
|
+
`Vercel request-logs retention starts at ${new Date(effectiveStartMs).toISOString()}, after requested start ${new Date(requestedStartMs).toISOString()}; refusing to advance because historical traffic would be skipped`
|
|
21962
|
+
);
|
|
21963
|
+
}
|
|
21784
21964
|
async function runBackfillTask(options) {
|
|
21785
21965
|
const {
|
|
21786
21966
|
app,
|
|
@@ -21960,8 +22140,12 @@ async function trafficRoutes(app, opts) {
|
|
|
21960
22140
|
const { address, family } = check.target;
|
|
21961
22141
|
return new UndiciAgent({
|
|
21962
22142
|
connect: {
|
|
21963
|
-
lookup: (_hostname,
|
|
21964
|
-
|
|
22143
|
+
lookup: (_hostname, options, cb) => {
|
|
22144
|
+
if (options?.all) {
|
|
22145
|
+
cb(null, [{ address, family: family === 6 ? 6 : 4 }]);
|
|
22146
|
+
} else {
|
|
22147
|
+
cb(null, address, family === 6 ? 6 : 4);
|
|
22148
|
+
}
|
|
21965
22149
|
}
|
|
21966
22150
|
}
|
|
21967
22151
|
});
|
|
@@ -22207,7 +22391,15 @@ async function trafficRoutes(app, opts) {
|
|
|
22207
22391
|
sourceType: TrafficSourceTypes.vercel,
|
|
22208
22392
|
displayName: fallbackName,
|
|
22209
22393
|
status: TrafficSourceStatuses.connected,
|
|
22210
|
-
lastSyncedAt
|
|
22394
|
+
// Seed lastSyncedAt to NOW so the first sync uses a tight window.
|
|
22395
|
+
// Leaving this null would make the first sync fall back to
|
|
22396
|
+
// DEFAULT_SYNC_WINDOW_MINUTES (30 days) — which exceeds Vercel's
|
|
22397
|
+
// request-logs retention (~14 days), causing the first sync to
|
|
22398
|
+
// throw a retention error and leaving the source permanently
|
|
22399
|
+
// stuck before it ever drained an event. New users opt into
|
|
22400
|
+
// historical recovery via the explicit `traffic backfill` command;
|
|
22401
|
+
// they do not silently inherit a 30-day pull on connect.
|
|
22402
|
+
lastSyncedAt: now,
|
|
22211
22403
|
lastCursor: null,
|
|
22212
22404
|
lastError: null,
|
|
22213
22405
|
archivedAt: null,
|
|
@@ -22416,6 +22608,9 @@ async function trafficRoutes(app, opts) {
|
|
|
22416
22608
|
pagesPerSubWindow: vercelMaxPages,
|
|
22417
22609
|
maxSubWindows: VERCEL_MAX_SUB_WINDOWS
|
|
22418
22610
|
});
|
|
22611
|
+
if (drained.retentionClamped) {
|
|
22612
|
+
throw vercelRetentionClampError(windowStart.getTime(), drained.effectiveStartMs);
|
|
22613
|
+
}
|
|
22419
22614
|
allEvents = drained.events;
|
|
22420
22615
|
} catch (e) {
|
|
22421
22616
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -22762,18 +22957,32 @@ async function trafficRoutes(app, opts) {
|
|
|
22762
22957
|
const vercelEnvironment = config.environment ?? credential.environment;
|
|
22763
22958
|
pullErrorPrefix = "Vercel pull failed";
|
|
22764
22959
|
pullForBackfill = async () => {
|
|
22765
|
-
const
|
|
22766
|
-
|
|
22767
|
-
|
|
22768
|
-
|
|
22769
|
-
|
|
22770
|
-
|
|
22771
|
-
|
|
22772
|
-
|
|
22773
|
-
|
|
22774
|
-
|
|
22775
|
-
|
|
22776
|
-
|
|
22960
|
+
const collected = [];
|
|
22961
|
+
const seenEventIds = /* @__PURE__ */ new Set();
|
|
22962
|
+
const backfillEndMs = windowEnd.getTime();
|
|
22963
|
+
for (let chunkStartMs = windowStart.getTime(); chunkStartMs < backfillEndMs; chunkStartMs += VERCEL_BACKFILL_CHUNK_MS) {
|
|
22964
|
+
const chunkEndMs = Math.min(chunkStartMs + VERCEL_BACKFILL_CHUNK_MS, backfillEndMs);
|
|
22965
|
+
const drained = await drainVercelTrafficEvents({
|
|
22966
|
+
pull: pullVercelEvents,
|
|
22967
|
+
token: credential.token,
|
|
22968
|
+
projectId: vercelProjectId,
|
|
22969
|
+
teamId: vercelTeamId,
|
|
22970
|
+
environment: vercelEnvironment,
|
|
22971
|
+
startDate: chunkStartMs,
|
|
22972
|
+
endDate: chunkEndMs,
|
|
22973
|
+
pagesPerSubWindow: BACKFILL_MAX_PAGES,
|
|
22974
|
+
maxSubWindows: VERCEL_MAX_SUB_WINDOWS
|
|
22975
|
+
});
|
|
22976
|
+
if (drained.retentionClamped) {
|
|
22977
|
+
throw vercelRetentionClampError(chunkStartMs, drained.effectiveStartMs);
|
|
22978
|
+
}
|
|
22979
|
+
for (const event of drained.events) {
|
|
22980
|
+
if (seenEventIds.has(event.eventId)) continue;
|
|
22981
|
+
seenEventIds.add(event.eventId);
|
|
22982
|
+
collected.push(event);
|
|
22983
|
+
}
|
|
22984
|
+
}
|
|
22985
|
+
return collected;
|
|
22777
22986
|
};
|
|
22778
22987
|
}
|
|
22779
22988
|
const startedAt = windowEnd.toISOString();
|
|
@@ -22859,6 +23068,43 @@ async function trafficRoutes(app, opts) {
|
|
|
22859
23068
|
} : null
|
|
22860
23069
|
};
|
|
22861
23070
|
}
|
|
23071
|
+
app.post("/projects/:name/traffic/sources/:id/reset", async (request) => {
|
|
23072
|
+
const project = resolveProject(app.db, request.params.name);
|
|
23073
|
+
const parsed = trafficResetRequestSchema.safeParse(request.body ?? {});
|
|
23074
|
+
if (!parsed.success) {
|
|
23075
|
+
throw validationError(
|
|
23076
|
+
"`advanceToNow` must be `true`. There is no implicit reset."
|
|
23077
|
+
);
|
|
23078
|
+
}
|
|
23079
|
+
const sourceRow = app.db.select().from(trafficSources).where(and19(eq24(trafficSources.projectId, project.id), eq24(trafficSources.id, request.params.id))).get();
|
|
23080
|
+
if (!sourceRow) {
|
|
23081
|
+
throw notFound("traffic source", request.params.id);
|
|
23082
|
+
}
|
|
23083
|
+
if (sourceRow.status === TrafficSourceStatuses.archived) {
|
|
23084
|
+
throw validationError(
|
|
23085
|
+
`Traffic source "${sourceRow.id}" is archived. Re-connect via "canonry traffic connect ..." to start tracking it again.`
|
|
23086
|
+
);
|
|
23087
|
+
}
|
|
23088
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23089
|
+
let updatedRow;
|
|
23090
|
+
app.db.transaction((tx) => {
|
|
23091
|
+
tx.update(trafficSources).set({
|
|
23092
|
+
lastSyncedAt: now,
|
|
23093
|
+
status: TrafficSourceStatuses.connected,
|
|
23094
|
+
lastError: null,
|
|
23095
|
+
updatedAt: now
|
|
23096
|
+
}).where(eq24(trafficSources.id, sourceRow.id)).run();
|
|
23097
|
+
writeAuditLog(tx, auditFromRequest(request, {
|
|
23098
|
+
projectId: project.id,
|
|
23099
|
+
actor: "api",
|
|
23100
|
+
action: "traffic.source.reset",
|
|
23101
|
+
entityType: "traffic_source",
|
|
23102
|
+
entityId: sourceRow.id
|
|
23103
|
+
}));
|
|
23104
|
+
updatedRow = tx.select().from(trafficSources).where(eq24(trafficSources.id, sourceRow.id)).get();
|
|
23105
|
+
});
|
|
23106
|
+
return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
|
|
23107
|
+
});
|
|
22862
23108
|
app.get("/projects/:name/traffic/sources", async (request) => {
|
|
22863
23109
|
const project = resolveProject(app.db, request.params.name);
|
|
22864
23110
|
const rows = app.db.select().from(trafficSources).where(eq24(trafficSources.projectId, project.id)).orderBy(desc13(trafficSources.createdAt)).all();
|
|
@@ -27694,166 +27940,6 @@ function buildRunCompletedProps(input) {
|
|
|
27694
27940
|
return props;
|
|
27695
27941
|
}
|
|
27696
27942
|
|
|
27697
|
-
// src/citation-utils.ts
|
|
27698
|
-
function domainMatches(domain, canonicalDomain) {
|
|
27699
|
-
const normalized = normalizeProjectDomain(canonicalDomain);
|
|
27700
|
-
const d = normalizeProjectDomain(domain);
|
|
27701
|
-
return d === normalized || d.endsWith(`.${normalized}`);
|
|
27702
|
-
}
|
|
27703
|
-
function determineCitationState(normalized, domains) {
|
|
27704
|
-
for (const canonicalDomain of domains) {
|
|
27705
|
-
const bareDomain = normalizeProjectDomain(canonicalDomain);
|
|
27706
|
-
if (normalized.citedDomains.some((d) => domainMatches(d, bareDomain))) {
|
|
27707
|
-
return "cited";
|
|
27708
|
-
}
|
|
27709
|
-
const lowerDomain = bareDomain.toLowerCase();
|
|
27710
|
-
for (const source of normalized.groundingSources) {
|
|
27711
|
-
try {
|
|
27712
|
-
const uri = source.uri.toLowerCase();
|
|
27713
|
-
if (lowerDomain.includes(".") && uri.includes(lowerDomain)) {
|
|
27714
|
-
return "cited";
|
|
27715
|
-
}
|
|
27716
|
-
} catch {
|
|
27717
|
-
}
|
|
27718
|
-
if (source.title) {
|
|
27719
|
-
const titleLower = source.title.toLowerCase().replace(/^www\./, "");
|
|
27720
|
-
if (titleLower === lowerDomain || titleLower.endsWith(`.${lowerDomain}`)) {
|
|
27721
|
-
return "cited";
|
|
27722
|
-
}
|
|
27723
|
-
}
|
|
27724
|
-
}
|
|
27725
|
-
}
|
|
27726
|
-
return "not-cited";
|
|
27727
|
-
}
|
|
27728
|
-
function computeCompetitorOverlap(normalized, competitorDomains) {
|
|
27729
|
-
const overlapSet = /* @__PURE__ */ new Set();
|
|
27730
|
-
for (const d of normalized.citedDomains) {
|
|
27731
|
-
for (const cd of competitorDomains) {
|
|
27732
|
-
if (domainMatches(d, cd)) {
|
|
27733
|
-
overlapSet.add(cd);
|
|
27734
|
-
}
|
|
27735
|
-
}
|
|
27736
|
-
}
|
|
27737
|
-
for (const source of normalized.groundingSources) {
|
|
27738
|
-
const uri = source.uri.toLowerCase();
|
|
27739
|
-
for (const cd of competitorDomains) {
|
|
27740
|
-
if (uri.includes(cd.toLowerCase())) {
|
|
27741
|
-
overlapSet.add(cd);
|
|
27742
|
-
}
|
|
27743
|
-
}
|
|
27744
|
-
}
|
|
27745
|
-
if (normalized.answerText) {
|
|
27746
|
-
const lowerAnswer = normalized.answerText.toLowerCase();
|
|
27747
|
-
for (const cd of competitorDomains) {
|
|
27748
|
-
if (lowerAnswer.includes(cd.toLowerCase())) {
|
|
27749
|
-
overlapSet.add(cd);
|
|
27750
|
-
}
|
|
27751
|
-
const brand = brandLabelFromDomain(cd);
|
|
27752
|
-
if (brand.length >= 4 && new RegExp(`\\b${escapeRegExp5(brand)}\\b`, "i").test(lowerAnswer)) {
|
|
27753
|
-
overlapSet.add(cd);
|
|
27754
|
-
}
|
|
27755
|
-
}
|
|
27756
|
-
}
|
|
27757
|
-
return [...overlapSet];
|
|
27758
|
-
}
|
|
27759
|
-
function escapeRegExp5(value) {
|
|
27760
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
27761
|
-
}
|
|
27762
|
-
function extractRecommendedCompetitors(answerText, ownDomains, citedDomains, competitorDomains, ownBrandNames = []) {
|
|
27763
|
-
if (!answerText || answerText.length < 20) return [];
|
|
27764
|
-
const ownBrandKeys = new Set(
|
|
27765
|
-
ownDomains.flatMap((domain) => collectBrandKeysFromDomain(domain))
|
|
27766
|
-
);
|
|
27767
|
-
for (const name of ownBrandNames) {
|
|
27768
|
-
const key = brandKeyFromText(name);
|
|
27769
|
-
if (key.length >= 4) ownBrandKeys.add(key);
|
|
27770
|
-
}
|
|
27771
|
-
const knownCompetitorKeys = new Set(
|
|
27772
|
-
[...citedDomains, ...competitorDomains].flatMap((domain) => collectBrandKeysFromDomain(domain)).filter((key) => !ownBrandKeys.has(key))
|
|
27773
|
-
);
|
|
27774
|
-
if (knownCompetitorKeys.size === 0) return [];
|
|
27775
|
-
const candidatePatterns = [
|
|
27776
|
-
/^\s*(?:[-*]|\d+\.)\s+(?:\*\*)?([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50}?)(?:\*\*)?\s*[:\u2014\u2013-]/gm,
|
|
27777
|
-
/\*\*([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50})\*\*/g,
|
|
27778
|
-
/^#{1,4}\s+(?:\d+\.\s+)?(?:\*\*)?([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50}?)(?:\*\*)?$/gm,
|
|
27779
|
-
/\[([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50})\]\(https?:\/\/[^\s)]+\)/g
|
|
27780
|
-
];
|
|
27781
|
-
const genericKeys = /* @__PURE__ */ new Set([
|
|
27782
|
-
"additional",
|
|
27783
|
-
"best",
|
|
27784
|
-
"benefits",
|
|
27785
|
-
"bottomline",
|
|
27786
|
-
"comparison",
|
|
27787
|
-
"conclusion",
|
|
27788
|
-
"directorylisting",
|
|
27789
|
-
"example",
|
|
27790
|
-
"expertise",
|
|
27791
|
-
"features",
|
|
27792
|
-
"finalthoughts",
|
|
27793
|
-
"howitworks",
|
|
27794
|
-
"important",
|
|
27795
|
-
"keybenefits",
|
|
27796
|
-
"keyfeatures",
|
|
27797
|
-
"major",
|
|
27798
|
-
"note",
|
|
27799
|
-
"notable",
|
|
27800
|
-
"option",
|
|
27801
|
-
"other",
|
|
27802
|
-
"overview",
|
|
27803
|
-
"pricing",
|
|
27804
|
-
"pros",
|
|
27805
|
-
"reviews",
|
|
27806
|
-
"step",
|
|
27807
|
-
"summary",
|
|
27808
|
-
"top",
|
|
27809
|
-
"verdict",
|
|
27810
|
-
"whattolookfor",
|
|
27811
|
-
"whyitmatters",
|
|
27812
|
-
"whyitstandsout",
|
|
27813
|
-
"whywechoseit"
|
|
27814
|
-
]);
|
|
27815
|
-
const seen = /* @__PURE__ */ new Map();
|
|
27816
|
-
for (const pattern of candidatePatterns) {
|
|
27817
|
-
let match;
|
|
27818
|
-
while ((match = pattern.exec(answerText)) !== null) {
|
|
27819
|
-
const candidate = cleanCandidateName(match[1] ?? "");
|
|
27820
|
-
const candidateKey = brandKeyFromText(candidate);
|
|
27821
|
-
if (!candidateKey) continue;
|
|
27822
|
-
if (genericKeys.has(candidateKey)) continue;
|
|
27823
|
-
if (candidate.split(/\s+/).length > 6) continue;
|
|
27824
|
-
if (matchesBrandKey(candidateKey, ownBrandKeys)) continue;
|
|
27825
|
-
if (!matchesBrandKey(candidateKey, knownCompetitorKeys)) continue;
|
|
27826
|
-
if (!seen.has(candidateKey)) seen.set(candidateKey, candidate);
|
|
27827
|
-
}
|
|
27828
|
-
}
|
|
27829
|
-
return [...seen.values()].slice(0, 10);
|
|
27830
|
-
}
|
|
27831
|
-
function cleanCandidateName(candidate) {
|
|
27832
|
-
return candidate.replace(/^[\s"'`]+|[\s"'`.,:;!?]+$/g, "").replace(/\s+/g, " ").trim();
|
|
27833
|
-
}
|
|
27834
|
-
function collectBrandKeysFromDomain(domain) {
|
|
27835
|
-
const reg = registrableDomain(domain);
|
|
27836
|
-
if (!reg) {
|
|
27837
|
-
const hostname = normalizeProjectDomain(domain).split("/")[0] ?? "";
|
|
27838
|
-
const fallback = hostname.replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
27839
|
-
return fallback.length >= 4 ? [fallback] : [];
|
|
27840
|
-
}
|
|
27841
|
-
const keys = /* @__PURE__ */ new Set();
|
|
27842
|
-
const fullKey = reg.replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
27843
|
-
if (fullKey.length >= 4) keys.add(fullKey);
|
|
27844
|
-
const brand = brandLabelFromDomain(reg).replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
27845
|
-
if (brand.length >= 4) keys.add(brand);
|
|
27846
|
-
return [...keys];
|
|
27847
|
-
}
|
|
27848
|
-
function matchesBrandKey(candidateKey, brandKeys) {
|
|
27849
|
-
for (const brandKey of brandKeys) {
|
|
27850
|
-
if (candidateKey === brandKey) return true;
|
|
27851
|
-
if (candidateKey.startsWith(brandKey) || candidateKey.endsWith(brandKey)) return true;
|
|
27852
|
-
if (brandKey.startsWith(candidateKey) || brandKey.endsWith(candidateKey)) return true;
|
|
27853
|
-
}
|
|
27854
|
-
return false;
|
|
27855
|
-
}
|
|
27856
|
-
|
|
27857
27943
|
// src/job-runner.ts
|
|
27858
27944
|
var log = createLogger("JobRunner");
|
|
27859
27945
|
var RunCancelledError = class extends Error {
|
|
@@ -30063,7 +30149,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
30063
30149
|
return result;
|
|
30064
30150
|
}
|
|
30065
30151
|
async function backfillInsightsCommand(project, opts) {
|
|
30066
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
30152
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-NY3MAVPB.js");
|
|
30067
30153
|
const config = loadConfig();
|
|
30068
30154
|
const db = createClient(config.database);
|
|
30069
30155
|
migrate(db);
|
|
@@ -33077,7 +33163,7 @@ function buildFallbackRecommendedActions(audit) {
|
|
|
33077
33163
|
function citesTargetDomain(citedDomains, groundingSources, targetDomain) {
|
|
33078
33164
|
const normalizedTarget = extractHostname2(targetDomain);
|
|
33079
33165
|
for (const domain of citedDomains) {
|
|
33080
|
-
if (
|
|
33166
|
+
if (domainMatches(domain, normalizedTarget)) {
|
|
33081
33167
|
return true;
|
|
33082
33168
|
}
|
|
33083
33169
|
}
|
|
@@ -33098,8 +33184,8 @@ function extractCompetitorsFromResponse(ctx) {
|
|
|
33098
33184
|
for (const hint of ctx.manualCompetitors) {
|
|
33099
33185
|
if (isDomainLike(hint)) {
|
|
33100
33186
|
const normalizedHint = normalizeDomain3(hint);
|
|
33101
|
-
if (
|
|
33102
|
-
if (ctx.citedDomains.some((domain) =>
|
|
33187
|
+
if (domainMatches(normalizedHint, targetDomain)) continue;
|
|
33188
|
+
if (ctx.citedDomains.some((domain) => domainMatches(domain, normalizedHint)) || lowerAnswer.includes(normalizedHint.toLowerCase())) {
|
|
33103
33189
|
competitors2.add(normalizedHint);
|
|
33104
33190
|
}
|
|
33105
33191
|
continue;
|
|
@@ -33169,7 +33255,7 @@ function normalizeDomain3(value) {
|
|
|
33169
33255
|
function extractHostname2(value) {
|
|
33170
33256
|
return normalizeDomain3(value);
|
|
33171
33257
|
}
|
|
33172
|
-
function
|
|
33258
|
+
function domainMatches(candidate, target) {
|
|
33173
33259
|
const normalizedCandidate = normalizeDomain3(candidate);
|
|
33174
33260
|
const normalizedTarget = normalizeDomain3(target);
|
|
33175
33261
|
return normalizedCandidate === normalizedTarget || normalizedCandidate.endsWith(`.${normalizedTarget}`);
|
|
@@ -33465,6 +33551,7 @@ async function createServer(opts) {
|
|
|
33465
33551
|
keyUrl: adapter.keyUrl,
|
|
33466
33552
|
modelHint: `e.g. ${adapter.modelRegistry.defaultModel}`,
|
|
33467
33553
|
model: registry.get(adapter.name)?.config.model,
|
|
33554
|
+
defaultModel: adapter.modelRegistry.defaultModel,
|
|
33468
33555
|
configured: !!registry.get(adapter.name),
|
|
33469
33556
|
quota: registry.get(adapter.name)?.config.quotaPolicy,
|
|
33470
33557
|
vertexConfigured: adapter.name === "gemini" ? !!opts.config.providers?.gemini?.vertexProject : void 0
|