@canonry/canonry 7.13.0 → 7.15.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/canonry/references/canonry-cli.md +19 -9
- package/assets/assets/AuditHistoryPanel-Cl9tUvSZ.js +1 -0
- package/assets/assets/{BacklinksPage-BQxB_eFt.js → BacklinksPage-CTeTJ9dh.js} +1 -1
- package/assets/assets/{HistoryPage-DgGUM2tr.js → HistoryPage-DpOoXr2P.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-Cl91vUu5.js → MeasurementPropertyPage-DxXsgM25.js} +1 -1
- package/assets/assets/ProjectPage-By9arjLJ.js +8 -0
- package/assets/assets/{RunsPage-BtjVuurK.js → RunsPage-CfDi7I1z.js} +1 -1
- package/assets/assets/SettingsPage-BHDb1apK.js +1 -0
- package/assets/assets/{SiteHealthSection-DMHWsfM4.js → SiteHealthSection-DQZyy4P_.js} +3 -3
- package/assets/assets/{TrafficPage-Bf0NkbIR.js → TrafficPage-DX_vN2RL.js} +2 -2
- package/assets/assets/{TrafficSourceDetailPage-BtwOdTAO.js → TrafficSourceDetailPage-Cg92zrtZ.js} +1 -1
- package/assets/assets/{extract-error-message-Cyc59Xi7.js → extract-error-message-BKxVCbov.js} +1 -1
- package/assets/assets/index-DoQJETc6.js +86 -0
- package/assets/assets/{index-D4QB1Igv.css → index-aBS1f2Bj.css} +1 -1
- package/assets/assets/{react-sigma_core.esm.min-CFB4bYg_.js → react-sigma_core.esm.min-BhqZcSgh.js} +1 -1
- package/assets/assets/{v2-overview-adapter-CLF10Pij.js → v2-overview-adapter-DZ5YsE_G.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-GQMSJEMZ.js → chunk-7HJYGDRW.js} +384 -57
- package/dist/{chunk-AU7OUDWR.js → chunk-KOBK4I5N.js} +5677 -5412
- package/dist/{chunk-BTHEFJCI.js → chunk-LC7GMHOL.js} +6 -6
- package/dist/{chunk-GBH7N3VZ.js → chunk-N2MRW2DH.js} +93 -31
- package/dist/{chunk-7I64KEGM.js → chunk-SG6T3DEP.js} +2 -2
- package/dist/{chunk-SM6ZZAPK.js → chunk-SGQBPTWQ.js} +2 -2
- package/dist/{chunk-EPSHIH63.js → chunk-YFCKHEKM.js} +3 -3
- package/dist/cli.js +1339 -1240
- package/dist/{demo-server-WJUA4EIG.js → demo-server-YWGUGZMI.js} +4 -4
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-DJRH4Z3D.js → intelligence-service-7RKUUR7O.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +11 -11
- package/assets/assets/AuditHistoryPanel-BYxGn9mS.js +0 -1
- package/assets/assets/ProjectPage-B_S900QN.js +0 -8
- package/assets/assets/SettingsPage-p0x0ZwGY.js +0 -1
- package/assets/assets/index-DR_kNdE4.js +0 -86
|
@@ -139,7 +139,7 @@ import {
|
|
|
139
139
|
trafficSeriesGranularitySchema,
|
|
140
140
|
visibilityCompareSelectionSchema,
|
|
141
141
|
visibilityReportRequestSchema
|
|
142
|
-
} from "./chunk-
|
|
142
|
+
} from "./chunk-KOBK4I5N.js";
|
|
143
143
|
|
|
144
144
|
// src/cli-error.ts
|
|
145
145
|
function isMachineFormat(format) {
|
|
@@ -1482,7 +1482,7 @@ var canonryMcpTools = [
|
|
|
1482
1482
|
defineTool({
|
|
1483
1483
|
name: "canonry_project_overview",
|
|
1484
1484
|
title: "Get project overview (composite)",
|
|
1485
|
-
description: `Sentiment headline is sentiment.branded; nonBrand is a separate exception population. pooledOverall and deprecated overall are explicitly pooled compatibility figures and must never be reported as a class-specific headline. One-call summary for "how is project X doing?". Returns independent mention and citation coverage, separate query-level movement for each signal, query-basket comparability with added/removed counts, latest run and health, insights, provider/model breakdowns, competitors, attention items, and recent history. Movement excludes queries not shared by both sweeps. Filterable by location and time window. Prefer this over fanning out to separate read tools for the same question. queryClassScope says which figures pool branded and non-brand: the coverage, scores (except scores.mentionShare) and movement count queries across both classes, so never report them as one class or as answers. The latest run's status and health are not a completeness check: for expected, executed and missing answers read canonry_measurement_data_quality or, for a plan run, canonry_run_completeness. canonry_doctor checks configuration and integrations, not sweeps. SCOPE LIMIT: this returns nothing about Advanced Measurement: no plan, Targets, Properties, groups, or segments. Its silence is not evidence that a project lacks them. When the system prompt has no Project shape line, call canonry_measurement_plan_get before stating that a project has no Properties, no Advanced plan, or cannot break out per-Property performance; use canonry_measurement_portfolio_summary to rank Properties.`,
|
|
1485
|
+
description: `Sentiment headline is sentiment.branded; nonBrand is a separate exception population. pooledOverall and deprecated overall are explicitly pooled compatibility figures and must never be reported as a class-specific headline. One-call summary for "how is project X doing?". Returns independent mention and citation coverage, separate query-level movement for each signal, query-basket comparability with added/removed counts, latest run and health, insights, provider/model breakdowns, competitors, attention items, and recent history. Movement excludes queries not shared by both sweeps. Filterable by location and time window. Prefer this over fanning out to separate read tools for the same question. queryClassScope says which figures pool branded and non-brand: the coverage, scores (except scores.mentionShare) and movement count queries across both classes, so never report them as one class or as answers. latestRun.admission describes the scheduled sweep; latestRun.manualAdmission describes a default full manual sweep. Each says whether its provider scope would be refused or skip providers that keep failing on their accounts. They can differ when the schedule names fewer providers; use manualAdmission before a default manual sweep. The latest run's status and health are not a completeness check: for expected, executed and missing answers read canonry_measurement_data_quality or, for a plan run, canonry_run_completeness. canonry_doctor checks configuration and integrations, not sweeps. SCOPE LIMIT: this returns nothing about Advanced Measurement: no plan, Targets, Properties, groups, or segments. Its silence is not evidence that a project lacks them. When the system prompt has no Project shape line, call canonry_measurement_plan_get before stating that a project has no Properties, no Advanced plan, or cannot break out per-Property performance; use canonry_measurement_portfolio_summary to rank Properties.`,
|
|
1486
1486
|
access: "read",
|
|
1487
1487
|
tier: "core",
|
|
1488
1488
|
inputSchema: z3.object({
|
|
@@ -1656,7 +1656,7 @@ var canonryMcpTools = [
|
|
|
1656
1656
|
defineTool({
|
|
1657
1657
|
name: "canonry_runs_latest",
|
|
1658
1658
|
title: "Get latest project run",
|
|
1659
|
-
description: "Get the latest run and total run count for a Canonry project.",
|
|
1659
|
+
description: "Get the latest run and total run count for a Canonry project, plus scheduled admission and default full-sweep manualAdmission: whether each would be refused (PROVIDERS_FAILING) and which providers it would skip because each keeps failing on its account, with each code and retryAfter. They can differ when the schedule names fewer providers. Read manualAdmission before a default manual sweep; admission describes the schedule. A refused scheduled sweep leaves no run, so read admission to tell whether scheduled sweeps are running.",
|
|
1660
1660
|
access: "read",
|
|
1661
1661
|
tier: "monitoring",
|
|
1662
1662
|
inputSchema: projectInputSchema,
|
|
@@ -1667,7 +1667,7 @@ var canonryMcpTools = [
|
|
|
1667
1667
|
defineTool({
|
|
1668
1668
|
name: "canonry_run_get",
|
|
1669
1669
|
title: "Get run",
|
|
1670
|
-
description: "Get a Canonry run with its snapshots.",
|
|
1670
|
+
description: "Get a Canonry run with its snapshots. skippedProviders records the providers this run did not call because they kept failing on their accounts; error.providers marks their entries skipped. This is a stored run, not the next sweep's admission: read canonry_runs_latest for scheduled admission and default full-sweep manualAdmission, which can differ when a schedule names fewer providers.",
|
|
1671
1671
|
access: "read",
|
|
1672
1672
|
tier: "monitoring",
|
|
1673
1673
|
inputSchema: runGetInputSchema,
|
|
@@ -2280,7 +2280,7 @@ var canonryMcpTools = [
|
|
|
2280
2280
|
defineTool({
|
|
2281
2281
|
name: "canonry_ga_measurement_analysis",
|
|
2282
2282
|
title: "Analyze GA acquisition and search demand",
|
|
2283
|
-
description: "Compare native GA4 channels and lead events with branded/non-brand Search Console demand over fixed 30-day cohorts.",
|
|
2283
|
+
description: "Compare native GA4 channels and lead events (also split by AI engine, with each engine's sessions and lead rate) with branded/non-brand Search Console demand over fixed 30-day cohorts.",
|
|
2284
2284
|
access: "read",
|
|
2285
2285
|
tier: "ga",
|
|
2286
2286
|
inputSchema: gaMeasurementAnalysisInputSchema,
|
|
@@ -3182,7 +3182,7 @@ var canonryMcpTools = [
|
|
|
3182
3182
|
defineTool({
|
|
3183
3183
|
name: "canonry_run_trigger",
|
|
3184
3184
|
title: "Trigger run",
|
|
3185
|
-
description: `Trigger an answer-visibility run for a Canonry project. Omit both scoping fields for a full sweep. Pass request.queries[] to measure only some of the project's tracked questions. On a project with a published measurement plan, pass request.measurementScope with at least one group or target to measure one slice of that plan \u2014 a group stands for its member Targets, and only the questions those Targets selected are measured. A scope naming nothing is rejected rather than treated as a full sweep, so omit the field entirely when you want everything. Either kind of slice is recorded as a spot check: it never replaces a full sweep in the dashboard, analytics or report. For verification scenarios (testing whether a provider migration worked, reproducing a regression, sanity-checking after a code change), set request.trigger='probe' \u2014 probe runs write a snapshot you can inspect via canonry_runs_get but are EXCLUDED from dashboard, analytics, intelligence, report, and notifications. Use 'probe' whenever you are testing on your own initiative rather than producing data the user/dashboard will consume. request.dispatchMode='batch' sends every provider that can (a full sweep of a published plan, a batch-capable provider enabled in config.yaml, every answer's model frozen) to its asynchronous batch API at the batch price; the rest run sync and the run stays running until the batch ends (up to its deadline, 24h by default). A batch request no provider can honour is refused with each provider's reason. Omit it for the ordinary sync sweep. A run is refused with PROVIDERS_FAILING when every provider it would call failed on its account (rejected key, denied access, no credit) in each of its last ${PROVIDER_ACCOUNT_FAILURE_STREAK} runs; details.providers names each provider's code and details.retryAfter when one run is let through again (${PROVIDER_ACCOUNT_RETRY_HOURS}h after the newest failure). Tell the user to fix the key, access or billing; saving a new key, model or endpoint in the provider's settings lets the next run through at once. A probe (request.trigger='probe') is never refused, and one that succeeds clears the refusal, so use a probe to check a fix. Set request.force=true only when the user asks to run anyway.`,
|
|
3185
|
+
description: `Trigger an answer-visibility run for a Canonry project. Omit both scoping fields for a full sweep. Pass request.queries[] to measure only some of the project's tracked questions. On a project with a published measurement plan, pass request.measurementScope with at least one group or target to measure one slice of that plan \u2014 a group stands for its member Targets, and only the questions those Targets selected are measured. A scope naming nothing is rejected rather than treated as a full sweep, so omit the field entirely when you want everything. Either kind of slice is recorded as a spot check: it never replaces a full sweep in the dashboard, analytics or report. For verification scenarios (testing whether a provider migration worked, reproducing a regression, sanity-checking after a code change), set request.trigger='probe' \u2014 probe runs write a snapshot you can inspect via canonry_runs_get but are EXCLUDED from dashboard, analytics, intelligence, report, and notifications. Use 'probe' whenever you are testing on your own initiative rather than producing data the user/dashboard will consume. request.dispatchMode='batch' sends every provider that can (a full sweep of a published plan, a batch-capable provider enabled in config.yaml, every answer's model frozen) to its asynchronous batch API at the batch price; the rest run sync and the run stays running until the batch ends (up to its deadline, 24h by default). A batch request no provider can honour is refused with each provider's reason. Omit it for the ordinary sync sweep. A run is refused with PROVIDERS_FAILING when every provider it would call failed on its account (rejected key, denied access, no credit) in each of its last ${PROVIDER_ACCOUNT_FAILURE_STREAK} runs; details.providers names each provider's code and details.retryAfter when one run is let through again (${PROVIDER_ACCOUNT_RETRY_HOURS}h after the newest failure). Tell the user to fix the key, access or billing; saving a new key, model or endpoint in the provider's settings lets the next run through at once. A probe (request.trigger='probe') is never refused, and one that succeeds clears the refusal, so use a probe to check a fix. When only some providers keep failing that way, the run is queued without them: skippedProviders on the returned run names each with its code and retryAfter, and the run ends partial. Set request.force=true (call every provider) only when the user asks to run anyway.`,
|
|
3186
3186
|
access: "write",
|
|
3187
3187
|
tier: "core",
|
|
3188
3188
|
inputSchema: runTriggerInputSchema,
|
|
@@ -20,20 +20,20 @@ import {
|
|
|
20
20
|
saveConfig,
|
|
21
21
|
saveConfigPatch,
|
|
22
22
|
sentimentInstallReadiness
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-SGQBPTWQ.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-SG6T3DEP.js";
|
|
31
31
|
import {
|
|
32
32
|
CanonryMcpToolNames,
|
|
33
33
|
CliError,
|
|
34
34
|
PACKAGE_VERSION,
|
|
35
35
|
canonryMcpTools
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-LC7GMHOL.js";
|
|
37
37
|
import {
|
|
38
38
|
CC_CACHE_DIR,
|
|
39
39
|
DUCKDB_SPEC,
|
|
@@ -217,7 +217,7 @@ import {
|
|
|
217
217
|
siteLivenessState,
|
|
218
218
|
toAlertView,
|
|
219
219
|
usageCounters
|
|
220
|
-
} from "./chunk-
|
|
220
|
+
} from "./chunk-7HJYGDRW.js";
|
|
221
221
|
import {
|
|
222
222
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
223
223
|
AGENT_NONE,
|
|
@@ -309,7 +309,6 @@ import {
|
|
|
309
309
|
canonicalizeGtmContainerId,
|
|
310
310
|
canonicalizeGtmResourceSelection,
|
|
311
311
|
classifyProviderErrorMessage,
|
|
312
|
-
classifyProviderErrorMessages,
|
|
313
312
|
classifyTemplateLinkEdge,
|
|
314
313
|
cloudflareQueueNameSchema,
|
|
315
314
|
compareSemver,
|
|
@@ -378,6 +377,7 @@ import {
|
|
|
378
377
|
maskTelemetryAnonymousId,
|
|
379
378
|
matcherMatchesText,
|
|
380
379
|
missingDependency,
|
|
380
|
+
mostActionableProviderErrorCode,
|
|
381
381
|
nextScheduleUpdatedAt,
|
|
382
382
|
normalizeAgentSlug,
|
|
383
383
|
normalizeIdTokens,
|
|
@@ -428,8 +428,9 @@ import {
|
|
|
428
428
|
validationError,
|
|
429
429
|
visibilityReportRequestSchema,
|
|
430
430
|
winnabilityClassLabel,
|
|
431
|
-
withRetry
|
|
432
|
-
|
|
431
|
+
withRetry,
|
|
432
|
+
withSkippedProviders
|
|
433
|
+
} from "./chunk-KOBK4I5N.js";
|
|
433
434
|
|
|
434
435
|
// src/runtime-context.ts
|
|
435
436
|
function cliRuntimeContext(env = process.env, stdio = process) {
|
|
@@ -8041,7 +8042,7 @@ function nonBlank(value) {
|
|
|
8041
8042
|
return trimmed ? trimmed : void 0;
|
|
8042
8043
|
}
|
|
8043
8044
|
function resolveBuildCommit(env = process.env) {
|
|
8044
|
-
const embedded = true ? "
|
|
8045
|
+
const embedded = true ? "e5577fdc9f99f2cc008aeff477aaf284da4532f7" : void 0;
|
|
8045
8046
|
return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
|
|
8046
8047
|
}
|
|
8047
8048
|
function resolveInstanceIdentity(env = process.env) {
|
|
@@ -8123,12 +8124,17 @@ function buildSiteAuditCompletedProps(input) {
|
|
|
8123
8124
|
return props;
|
|
8124
8125
|
}
|
|
8125
8126
|
var MAX_NESTED_KEYS = 12;
|
|
8126
|
-
function buildProviderOutcomeProps(providers, providerErrors) {
|
|
8127
|
-
if (providerErrors.size === 0) return {};
|
|
8128
|
-
const
|
|
8127
|
+
function buildProviderOutcomeProps(providers, providerErrors, skippedProviders = []) {
|
|
8128
|
+
if (providerErrors.size === 0 && skippedProviders.length === 0) return {};
|
|
8129
|
+
const skipped = new Set(skippedProviders);
|
|
8130
|
+
const names = [.../* @__PURE__ */ new Set([...providers, ...providerErrors.keys(), ...skipped])].slice(0, MAX_NESTED_KEYS);
|
|
8129
8131
|
const providerOutcomes = {};
|
|
8130
8132
|
const providerHttpStatus = {};
|
|
8131
8133
|
for (const name of names) {
|
|
8134
|
+
if (skipped.has(name)) {
|
|
8135
|
+
providerOutcomes[name] = "skipped";
|
|
8136
|
+
continue;
|
|
8137
|
+
}
|
|
8132
8138
|
const message = providerErrors.get(name);
|
|
8133
8139
|
if (message === void 0) {
|
|
8134
8140
|
providerOutcomes[name] = "ok";
|
|
@@ -8140,6 +8146,20 @@ function buildProviderOutcomeProps(providers, providerErrors) {
|
|
|
8140
8146
|
}
|
|
8141
8147
|
return Object.keys(providerHttpStatus).length > 0 ? { providerOutcomes, providerHttpStatus } : { providerOutcomes };
|
|
8142
8148
|
}
|
|
8149
|
+
function buildRunRefusedProps(input) {
|
|
8150
|
+
const outcomes = Object.entries(input.providers).slice(0, MAX_NESTED_KEYS);
|
|
8151
|
+
const props = {
|
|
8152
|
+
reason: "providers_failing",
|
|
8153
|
+
providerCount: Object.keys(input.providers).length,
|
|
8154
|
+
providers: Object.keys(input.providers),
|
|
8155
|
+
providerOutcomes: Object.fromEntries(outcomes),
|
|
8156
|
+
trigger: input.trigger
|
|
8157
|
+
};
|
|
8158
|
+
const domainHash = hashDomain(input.canonicalDomain ?? null);
|
|
8159
|
+
if (domainHash) props.domainHash = domainHash;
|
|
8160
|
+
if (input.location) props.location = input.location;
|
|
8161
|
+
return props;
|
|
8162
|
+
}
|
|
8143
8163
|
function runFailureSite(providerCallStart, providerCallEnd) {
|
|
8144
8164
|
if (providerCallStart === void 0) return "setup";
|
|
8145
8165
|
return providerCallEnd === void 0 ? "provider_call" : "finalize";
|
|
@@ -8455,12 +8475,16 @@ function chunkBatchLines(lines, maxRequests, maxBytes) {
|
|
|
8455
8475
|
if (current.length > 0) chunks.push(current);
|
|
8456
8476
|
return { chunks, oversized };
|
|
8457
8477
|
}
|
|
8458
|
-
function
|
|
8459
|
-
|
|
8478
|
+
function calledProviderErrors(providerErrors, skipped) {
|
|
8479
|
+
return new Map([...providerErrors].filter(([provider]) => !(provider in skipped)));
|
|
8480
|
+
}
|
|
8481
|
+
function runOutcome(inserted, providerErrors, planShortfall, skipped) {
|
|
8482
|
+
const errors = calledProviderErrors(providerErrors, skipped);
|
|
8483
|
+
const someFailed = errors.size > 0 || planShortfall > 0 || Object.keys(skipped).length > 0;
|
|
8460
8484
|
const allFailed = inserted === 0 && someFailed;
|
|
8461
8485
|
return {
|
|
8462
8486
|
status: allFailed ? RunStatuses.failed : someFailed ? RunStatuses.partial : RunStatuses.completed,
|
|
8463
|
-
error: someFailed ? serializeRunError(buildProviderRunError(
|
|
8487
|
+
error: someFailed ? serializeRunError(withSkippedProviders(buildProviderRunError(errors), skipped)) : null
|
|
8464
8488
|
};
|
|
8465
8489
|
}
|
|
8466
8490
|
function runRecordingContext(db, input) {
|
|
@@ -8518,8 +8542,12 @@ function classifyRunAbortReason(message) {
|
|
|
8518
8542
|
return void 0;
|
|
8519
8543
|
}
|
|
8520
8544
|
var FAILURE_STREAK_LOOKBACK = 50;
|
|
8521
|
-
function classifyProviderErrors(errors) {
|
|
8522
|
-
|
|
8545
|
+
function classifyProviderErrors(errors, skipped) {
|
|
8546
|
+
const codes = [
|
|
8547
|
+
...[...errors.values()].map(classifyProviderErrorMessage),
|
|
8548
|
+
...Object.values(skipped).map((streak) => streak.code)
|
|
8549
|
+
];
|
|
8550
|
+
return codes.length > 0 ? mostActionableProviderErrorCode(codes) : void 0;
|
|
8523
8551
|
}
|
|
8524
8552
|
var JobRunner = class {
|
|
8525
8553
|
/**
|
|
@@ -8739,7 +8767,14 @@ var JobRunner = class {
|
|
|
8739
8767
|
if (activeProviders.length === 0) {
|
|
8740
8768
|
throw new Error("No providers configured. Add at least one provider API key.");
|
|
8741
8769
|
}
|
|
8742
|
-
|
|
8770
|
+
const skipped = existingRun.skippedProviders ?? {};
|
|
8771
|
+
const calledProviders = activeProviders.filter((provider) => !(provider.adapter.name in skipped));
|
|
8772
|
+
log2.info("run.dispatch", {
|
|
8773
|
+
runId,
|
|
8774
|
+
providerCount: calledProviders.length,
|
|
8775
|
+
providers: calledProviders.map((p) => p.adapter.name),
|
|
8776
|
+
...Object.keys(skipped).length > 0 ? { skippedProviders: Object.keys(skipped) } : {}
|
|
8777
|
+
});
|
|
8743
8778
|
const projectCompetitors = this.db.select().from(competitors).where(eq3(competitors.projectId, projectId)).orderBy(competitors.domain).all();
|
|
8744
8779
|
const competitorDomains = projectCompetitors.map((c) => c.domain);
|
|
8745
8780
|
recording = runRecordingContext(this.db, {
|
|
@@ -8761,7 +8796,7 @@ var JobRunner = class {
|
|
|
8761
8796
|
};
|
|
8762
8797
|
const queriesPerProvider = planExecution?.maxUnitsPerProvider ?? projectQueries.length;
|
|
8763
8798
|
const todayPeriod = getCurrentUsageDay();
|
|
8764
|
-
for (const p of
|
|
8799
|
+
for (const p of calledProviders) {
|
|
8765
8800
|
const providerScope = `${projectId}:${p.adapter.name}`;
|
|
8766
8801
|
const limit = p.config.quotaPolicy.maxRequestsPerDay;
|
|
8767
8802
|
const quota = reserveDailyQueryQuota(this.db, { scope: providerScope, period: todayPeriod, count: queriesPerProvider, limit });
|
|
@@ -8773,7 +8808,7 @@ var JobRunner = class {
|
|
|
8773
8808
|
providerReservations.set(p.adapter.name, { scope: providerScope, period: todayPeriod, reserved: queriesPerProvider });
|
|
8774
8809
|
}
|
|
8775
8810
|
const executionGates = /* @__PURE__ */ new Map();
|
|
8776
|
-
for (const provider of
|
|
8811
|
+
for (const provider of calledProviders) {
|
|
8777
8812
|
executionGates.set(
|
|
8778
8813
|
provider.adapter.name,
|
|
8779
8814
|
getSharedProviderExecutionGate(
|
|
@@ -8785,8 +8820,8 @@ var JobRunner = class {
|
|
|
8785
8820
|
}
|
|
8786
8821
|
const providerErrors = /* @__PURE__ */ new Map();
|
|
8787
8822
|
let totalSnapshotsInserted = 0;
|
|
8788
|
-
const apiProviders =
|
|
8789
|
-
const browserProviders =
|
|
8823
|
+
const apiProviders = calledProviders.filter((p) => !isBrowserProvider(p.adapter.name));
|
|
8824
|
+
const browserProviders = calledProviders.filter((p) => isBrowserProvider(p.adapter.name));
|
|
8790
8825
|
const processQueryForProvider = async (registeredProvider, q) => {
|
|
8791
8826
|
const { adapter, config } = registeredProvider;
|
|
8792
8827
|
const providerName = adapter.name;
|
|
@@ -9052,7 +9087,7 @@ var JobRunner = class {
|
|
|
9052
9087
|
for (const [provider, units] of planExecution.unitsByProvider) {
|
|
9053
9088
|
const providerName = provider;
|
|
9054
9089
|
const dispatched = providerDispatchCounts.get(providerName) ?? 0;
|
|
9055
|
-
if (dispatched >= units.length || providerErrors.has(providerName)) continue;
|
|
9090
|
+
if (dispatched >= units.length || providerErrors.has(providerName) || providerName in skipped) continue;
|
|
9056
9091
|
providerErrors.set(
|
|
9057
9092
|
providerName,
|
|
9058
9093
|
`No ${provider} provider was available to this worker, so ${units.length - dispatched} expected measurement(s) did not run.`
|
|
@@ -9077,6 +9112,7 @@ var JobRunner = class {
|
|
|
9077
9112
|
inserted: totalSnapshotsInserted,
|
|
9078
9113
|
providerErrors,
|
|
9079
9114
|
planShortfall,
|
|
9115
|
+
skippedProviders: skipped,
|
|
9080
9116
|
executionContext,
|
|
9081
9117
|
startTime,
|
|
9082
9118
|
phases: buildPhases({ startTime, providerCallStart, providerCallEnd }),
|
|
@@ -9181,7 +9217,7 @@ var JobRunner = class {
|
|
|
9181
9217
|
*/
|
|
9182
9218
|
finalizeRun(input) {
|
|
9183
9219
|
const { runId, providerErrors } = input;
|
|
9184
|
-
const outcome = runOutcome(input.inserted, providerErrors, input.planShortfall);
|
|
9220
|
+
const outcome = runOutcome(input.inserted, providerErrors, input.planShortfall, input.skippedProviders ?? {});
|
|
9185
9221
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9186
9222
|
const won = this.db.transaction((tx) => {
|
|
9187
9223
|
const changed = tx.update(runs).set({
|
|
@@ -9207,8 +9243,10 @@ var JobRunner = class {
|
|
|
9207
9243
|
* post-run pipeline.
|
|
9208
9244
|
*/
|
|
9209
9245
|
reportFinalizedRun(input, finalStatus) {
|
|
9210
|
-
const { runId, projectId,
|
|
9211
|
-
const
|
|
9246
|
+
const { runId, projectId, executionContext } = input;
|
|
9247
|
+
const skippedProviders = input.skippedProviders ?? {};
|
|
9248
|
+
const providerErrors = calledProviderErrors(input.providerErrors, skippedProviders);
|
|
9249
|
+
const failureCode = classifyProviderErrors(providerErrors, skippedProviders);
|
|
9212
9250
|
const sampling = failureStreakSampling(finalStatus, this.priorFailureStreak(projectId, runId), runId);
|
|
9213
9251
|
if (sampling.report) {
|
|
9214
9252
|
trackEvent(
|
|
@@ -9225,7 +9263,7 @@ var JobRunner = class {
|
|
|
9225
9263
|
phases: input.phases,
|
|
9226
9264
|
location: executionContext.location
|
|
9227
9265
|
}),
|
|
9228
|
-
...buildProviderOutcomeProps(executionContext.providers, providerErrors),
|
|
9266
|
+
...buildProviderOutcomeProps(executionContext.providers, providerErrors, Object.keys(skippedProviders)),
|
|
9229
9267
|
...sampling.props
|
|
9230
9268
|
},
|
|
9231
9269
|
failureCode ? { errorCode: failureCode } : void 0
|
|
@@ -9295,15 +9333,21 @@ var JobRunner = class {
|
|
|
9295
9333
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9296
9334
|
const decided = this.db.transaction((tx) => {
|
|
9297
9335
|
const txDb = tx;
|
|
9298
|
-
const run = txDb.select({
|
|
9336
|
+
const run = txDb.select({
|
|
9337
|
+
kind: runs.kind,
|
|
9338
|
+
status: runs.status,
|
|
9339
|
+
pendingProviderErrors: runs.pendingProviderErrors,
|
|
9340
|
+
skippedProviders: runs.skippedProviders
|
|
9341
|
+
}).from(runs).where(and3(eq3(runs.id, runId), eq3(runs.projectId, projectId))).get();
|
|
9299
9342
|
if (!run || run.status !== RunStatuses.running || run.pendingProviderErrors === null) return null;
|
|
9300
9343
|
const holding = txDb.select({ id: providerBatches.id }).from(providerBatches).where(and3(eq3(providerBatches.runId, runId), inArray(providerBatches.status, RUN_HOLDING_BATCH_STATUSES))).limit(1).get();
|
|
9301
9344
|
if (holding) return null;
|
|
9302
9345
|
const state = measurementRunSlotState(txDb, runId);
|
|
9303
9346
|
const inserted = txDb.select({ value: count() }).from(querySnapshots).where(eq3(querySnapshots.runId, runId)).get()?.value ?? 0;
|
|
9304
9347
|
const providerErrors = this.batchRunProviderErrors(txDb, runId, run.pendingProviderErrors, state.missing);
|
|
9348
|
+
const skippedProviders = run.skippedProviders ?? {};
|
|
9305
9349
|
const shortfall = state.missing.length + (state.readable ? 0 : 1);
|
|
9306
|
-
const outcome = runOutcome(inserted, providerErrors, shortfall);
|
|
9350
|
+
const outcome = runOutcome(inserted, providerErrors, shortfall, skippedProviders);
|
|
9307
9351
|
const won = txDb.update(runs).set({
|
|
9308
9352
|
status: outcome.status,
|
|
9309
9353
|
finishedAt,
|
|
@@ -9312,7 +9356,7 @@ var JobRunner = class {
|
|
|
9312
9356
|
}).where(and3(eq3(runs.id, runId), eq3(runs.status, RunStatuses.running), isNotNull(runs.pendingProviderErrors))).run().changes === 1;
|
|
9313
9357
|
if (!won) return null;
|
|
9314
9358
|
recordSentimentCompletion(tx, { projectId, runId, completionKey: "initial", completedAt: finishedAt });
|
|
9315
|
-
return { kind: run.kind, inserted, providerErrors, status: outcome.status };
|
|
9359
|
+
return { kind: run.kind, inserted, providerErrors, skippedProviders, status: outcome.status };
|
|
9316
9360
|
});
|
|
9317
9361
|
if (!decided) return false;
|
|
9318
9362
|
const telemetry = this.batchRunTelemetry(runId, projectId);
|
|
@@ -9323,6 +9367,7 @@ var JobRunner = class {
|
|
|
9323
9367
|
kind: decided.kind,
|
|
9324
9368
|
inserted: decided.inserted,
|
|
9325
9369
|
providerErrors: decided.providerErrors,
|
|
9370
|
+
skippedProviders: decided.skippedProviders,
|
|
9326
9371
|
executionContext: telemetry.executionContext,
|
|
9327
9372
|
startTime: telemetry.startTime,
|
|
9328
9373
|
// The sweep's phase split does not survive the wait (or a restart);
|
|
@@ -10373,7 +10418,8 @@ var JobRunner = class {
|
|
|
10373
10418
|
measurementPlanVersionId: runs.measurementPlanVersionId,
|
|
10374
10419
|
measurementManifest: runs.measurementManifest,
|
|
10375
10420
|
providerDispatchModes: runs.providerDispatchModes,
|
|
10376
|
-
pendingProviderErrors: runs.pendingProviderErrors
|
|
10421
|
+
pendingProviderErrors: runs.pendingProviderErrors,
|
|
10422
|
+
skippedProviders: runs.skippedProviders
|
|
10377
10423
|
}).from(runs).where(eq3(runs.id, runId)).get();
|
|
10378
10424
|
}
|
|
10379
10425
|
isRunCancelled(runId) {
|
|
@@ -15120,6 +15166,9 @@ var Scheduler = class {
|
|
|
15120
15166
|
runnableProviders: this.callbacks.getRunnableProviderNames?.(),
|
|
15121
15167
|
providerModels: this.callbacks.getEffectiveProviderModels?.(),
|
|
15122
15168
|
batchEligibleProviders: this.callbacks.getBatchEligibleProviderNames?.() ?? null,
|
|
15169
|
+
// A skipped slot is otherwise invisible: no run row, nothing in the
|
|
15170
|
+
// dashboard. One audit row per refusal keeps it in the history.
|
|
15171
|
+
auditRefusal: { actor: "scheduler", entityType: "schedule", entityId: currentSchedule.id },
|
|
15123
15172
|
...claimedOccurrence && recurrence && currentSchedule.nextRunAt === claimedOccurrence ? {
|
|
15124
15173
|
scheduleClaim: {
|
|
15125
15174
|
scheduleId: currentSchedule.id,
|
|
@@ -15149,6 +15198,14 @@ var Scheduler = class {
|
|
|
15149
15198
|
scheduleId: currentSchedule.id,
|
|
15150
15199
|
...queueResult.refused
|
|
15151
15200
|
});
|
|
15201
|
+
if (queueResult.refusalRecorded) {
|
|
15202
|
+
trackEvent("run.aborted", buildRunRefusedProps({
|
|
15203
|
+
providers: queueResult.refused.providers,
|
|
15204
|
+
trigger: RunTriggers.scheduled,
|
|
15205
|
+
canonicalDomain: project.canonicalDomain,
|
|
15206
|
+
location: locationLabel
|
|
15207
|
+
}), { errorCode: "PROVIDERS_FAILING" });
|
|
15208
|
+
}
|
|
15152
15209
|
if (!claimedOccurrence) this.updateScheduleTiming(currentSchedule.id, { nextRunAt });
|
|
15153
15210
|
return;
|
|
15154
15211
|
}
|
|
@@ -15169,7 +15226,12 @@ var Scheduler = class {
|
|
|
15169
15226
|
lastRunAt: now,
|
|
15170
15227
|
nextRunAt
|
|
15171
15228
|
});
|
|
15172
|
-
log16.info("run.triggered", {
|
|
15229
|
+
log16.info("run.triggered", {
|
|
15230
|
+
runId,
|
|
15231
|
+
projectName: project.name,
|
|
15232
|
+
providers: providers ?? "all",
|
|
15233
|
+
...Object.keys(queueResult.skippedProviders).length > 0 ? { skippedProviders: queueResult.skippedProviders } : {}
|
|
15234
|
+
});
|
|
15173
15235
|
this.callbacks.onRunCreated(runId, projectId, providers, resolvedLocation);
|
|
15174
15236
|
} catch (err) {
|
|
15175
15237
|
log16.error("trigger.error", { scheduleId, projectId, kind, error: describeError(err) });
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
siteCrawlGraphNodes,
|
|
6
6
|
siteCrawlPages,
|
|
7
7
|
siteCrawlSnapshots
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-7HJYGDRW.js";
|
|
9
9
|
import {
|
|
10
10
|
SITE_CRAWL_GRAPH_MAX_EDGES,
|
|
11
11
|
SITE_CRAWL_GRAPH_MAX_NODES,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
factorStatusFromDistribution,
|
|
15
15
|
factorStatusFromScore,
|
|
16
16
|
roundPreservingTotal
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-KOBK4I5N.js";
|
|
18
18
|
|
|
19
19
|
// src/site-audit-factors.ts
|
|
20
20
|
function recordedSharePct(factor) {
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
PACKAGE_VERSION,
|
|
6
6
|
canonryMcpTools,
|
|
7
7
|
isMachineFormat
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-LC7GMHOL.js";
|
|
9
9
|
import {
|
|
10
10
|
AGENT_PROVIDER_IDS,
|
|
11
11
|
CANONRY_NPM_PACKAGE_URL,
|
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
skillsClientSchema,
|
|
51
51
|
upgradeCaveatFor,
|
|
52
52
|
upgradeCommandFor
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-KOBK4I5N.js";
|
|
54
54
|
|
|
55
55
|
// src/config.ts
|
|
56
56
|
import fs from "fs";
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
installSkills,
|
|
5
5
|
loadConfigRaw,
|
|
6
6
|
saveConfigPatch
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-SGQBPTWQ.js";
|
|
8
8
|
import {
|
|
9
9
|
PACKAGE_VERSION
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-LC7GMHOL.js";
|
|
11
11
|
import {
|
|
12
12
|
SKILL_MANIFEST_FILENAME,
|
|
13
13
|
SkillsClients
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-KOBK4I5N.js";
|
|
15
15
|
|
|
16
16
|
// src/skills-autosync.ts
|
|
17
17
|
import fs from "fs";
|