@ainyc/canonry 4.105.0 → 4.106.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +1 -0
  2. package/assets/assets/{BacklinksPage-Kls1x7Zh.js → BacklinksPage-bljd41XH.js} +1 -1
  3. package/assets/assets/{ChartPrimitives-DteNu5zR.js → ChartPrimitives-zcHZades.js} +1 -1
  4. package/assets/assets/{ProjectPage-edkqv28l.js → ProjectPage-DuC-ieuH.js} +1 -1
  5. package/assets/assets/{RunRow-DSQNNTkL.js → RunRow-DGHqXMAK.js} +1 -1
  6. package/assets/assets/{RunsPage-CNlAmkea.js → RunsPage-BxXIUvik.js} +1 -1
  7. package/assets/assets/{SettingsPage-CbZCrj9s.js → SettingsPage-ChP3HlH-.js} +1 -1
  8. package/assets/assets/{TrafficPage-BA27Ssn7.js → TrafficPage-DpskpsCQ.js} +1 -1
  9. package/assets/assets/{TrafficSourceDetailPage-ObEEBBgZ.js → TrafficSourceDetailPage-DA-l5sl7.js} +1 -1
  10. package/assets/assets/{arrow-left-C6gsO8Mi.js → arrow-left-DurzPS8N.js} +1 -1
  11. package/assets/assets/{extract-error-message-C8UnfNTz.js → extract-error-message-CYF6MaAP.js} +1 -1
  12. package/assets/assets/index-CBNeFIEW.css +1 -0
  13. package/assets/assets/{index-BFQXjVBq.js → index-DsTCC45f.js} +39 -39
  14. package/assets/assets/{trash-2-CX3yohiP.js → trash-2-C-eV0jO1.js} +1 -1
  15. package/assets/index.html +2 -2
  16. package/dist/{chunk-XRKGLZF6.js → chunk-2AWATO4I.js} +50 -7
  17. package/dist/{chunk-UCKUZCIM.js → chunk-F3MKVMS5.js} +62 -13
  18. package/dist/{chunk-IDSJTITA.js → chunk-VGMTLUUM.js} +4 -2
  19. package/dist/{chunk-LZW4ABXX.js → chunk-VOR5ZTUU.js} +50 -1
  20. package/dist/cli.js +21 -8
  21. package/dist/index.js +4 -4
  22. package/dist/{intelligence-service-UBGH5QF4.js → intelligence-service-JGZU27U3.js} +2 -2
  23. package/dist/mcp.js +2 -2
  24. package/package.json +10 -10
  25. package/assets/assets/index-LsyRFLMG.css +0 -1
@@ -1 +1 @@
1
- import{c}from"./index-BFQXjVBq.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
1
+ import{c}from"./index-DsTCC45f.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
package/assets/index.html CHANGED
@@ -12,12 +12,12 @@
12
12
  <link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
13
13
  <link rel="apple-touch-icon" href="./apple-touch-icon.png" />
14
14
  <title>Canonry</title>
15
- <script type="module" crossorigin src="./assets/index-BFQXjVBq.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-DsTCC45f.js"></script>
16
16
  <link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-Be8JdV5M.js">
17
17
  <link rel="modulepreload" crossorigin href="./assets/vendor-radix-CQ77EO2y.js">
18
18
  <link rel="modulepreload" crossorigin href="./assets/vendor-recharts-C9EZkgbP.js">
19
19
  <link rel="modulepreload" crossorigin href="./assets/vendor-markdown-6dwjPOKK.js">
20
- <link rel="stylesheet" crossorigin href="./assets/index-LsyRFLMG.css">
20
+ <link rel="stylesheet" crossorigin href="./assets/index-CBNeFIEW.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig,
11
11
  loadConfigRaw,
12
12
  saveConfigPatch
13
- } from "./chunk-IDSJTITA.js";
13
+ } from "./chunk-VGMTLUUM.js";
14
14
  import {
15
15
  CC_CACHE_DIR,
16
16
  DUCKDB_SPEC,
@@ -115,7 +115,7 @@ import {
115
115
  siteAuditPages,
116
116
  siteAuditSnapshots,
117
117
  usageCounters
118
- } from "./chunk-UCKUZCIM.js";
118
+ } from "./chunk-F3MKVMS5.js";
119
119
  import {
120
120
  AGENT_MEMORY_VALUE_MAX_BYTES,
121
121
  AGENT_PROVIDER_IDS,
@@ -178,7 +178,7 @@ import {
178
178
  validationError,
179
179
  winnabilityClassLabel,
180
180
  withRetry
181
- } from "./chunk-LZW4ABXX.js";
181
+ } from "./chunk-VOR5ZTUU.js";
182
182
 
183
183
  // src/telemetry.ts
184
184
  import crypto from "crypto";
@@ -5506,6 +5506,42 @@ import crypto13 from "crypto";
5506
5506
  import { and as and9, eq as eq11 } from "drizzle-orm";
5507
5507
  var log12 = createLogger("DiscoveryRun");
5508
5508
  var DEFAULT_SEED_COUNT = 30;
5509
+ var EMBED_RETRY_MAX_RETRIES = 3;
5510
+ var EMBED_RETRY_MAX_DELAY_MS = 1e4;
5511
+ var EMBED_ATTEMPT_TIMEOUT_MS = 6e4;
5512
+ function withAttemptTimeout(promise, timeoutMs, label) {
5513
+ return new Promise((resolve, reject) => {
5514
+ const timer = setTimeout(
5515
+ () => reject(new Error(`${label} timed out after ${timeoutMs}ms`)),
5516
+ timeoutMs
5517
+ );
5518
+ promise.then(
5519
+ (value) => {
5520
+ clearTimeout(timer);
5521
+ resolve(value);
5522
+ },
5523
+ (err) => {
5524
+ clearTimeout(timer);
5525
+ reject(err instanceof Error ? err : new Error(String(err)));
5526
+ }
5527
+ );
5528
+ });
5529
+ }
5530
+ async function embedWithRetry(fn, opts = {}) {
5531
+ const attemptTimeoutMs = opts.attemptTimeoutMs ?? EMBED_ATTEMPT_TIMEOUT_MS;
5532
+ return withRetry(
5533
+ () => withAttemptTimeout(fn(), attemptTimeoutMs, "Discovery embedding call"),
5534
+ {
5535
+ maxRetries: opts.maxRetries ?? EMBED_RETRY_MAX_RETRIES,
5536
+ isRetryable: isRetryableHttpError,
5537
+ maxDelayMs: EMBED_RETRY_MAX_DELAY_MS,
5538
+ sleep: opts.sleep,
5539
+ onRetry: opts.onRetry ?? (({ attempt, delayMs }) => {
5540
+ log12.warn("discovery.embed.retry", { attempt, delayMs });
5541
+ })
5542
+ }
5543
+ );
5544
+ }
5509
5545
  var QUERIES_PER_INTENT_BUCKET = 6;
5510
5546
  async function executeDiscoveryRun(opts) {
5511
5547
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -5538,6 +5574,7 @@ async function executeDiscoveryRun(opts) {
5538
5574
  icpDescription: opts.icpDescription,
5539
5575
  dedupThreshold: opts.dedupThreshold,
5540
5576
  maxProbes: opts.maxProbes,
5577
+ probeConcurrency: opts.probeConcurrency,
5541
5578
  locations: opts.locations,
5542
5579
  deps
5543
5580
  });
@@ -5592,12 +5629,17 @@ function buildDefaultDeps(registry) {
5592
5629
  const fromGrounding = normalized.searchQueries ?? [];
5593
5630
  return {
5594
5631
  candidates: [...fromAnswer, ...fromGrounding],
5595
- provider: "gemini"
5632
+ provider: "gemini",
5633
+ // Session diagnostics: the raw-candidate split by source, persisted on
5634
+ // discovery_sessions so seed-quality calibration is measurable.
5635
+ fromAnswerCount: fromAnswer.length,
5636
+ fromGroundingCount: fromGrounding.length
5596
5637
  };
5597
5638
  },
5598
5639
  async embed(queries2) {
5599
- if (cfg.apiKey) {
5600
- return embedQueries(queries2, { apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
5640
+ const apiKey = cfg.apiKey;
5641
+ if (apiKey) {
5642
+ return embedWithRetry(() => embedQueries(queries2, { apiKey, baseUrl: cfg.baseUrl }));
5601
5643
  }
5602
5644
  throw new Error("Discovery currently requires a Gemini API key. Vertex-mode embeddings are not yet implemented.");
5603
5645
  },
@@ -6431,7 +6473,7 @@ function readStoredGroundingSources(rawResponse) {
6431
6473
  return result;
6432
6474
  }
6433
6475
  async function backfillInsightsCommand(project, opts) {
6434
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-UBGH5QF4.js");
6476
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-JGZU27U3.js");
6435
6477
  const config = loadConfig();
6436
6478
  const db = createClient(config.database);
6437
6479
  migrate(db);
@@ -11898,6 +11940,7 @@ async function createServer(opts) {
11898
11940
  icpDescription: input.icpDescription,
11899
11941
  dedupThreshold: input.dedupThreshold,
11900
11942
  maxProbes: input.maxProbes,
11943
+ probeConcurrency: input.probeConcurrency,
11901
11944
  locations: input.locations
11902
11945
  }).then(() => runCoordinator.onRunCompleted(input.runId, input.projectId)).catch((err) => {
11903
11946
  app.log.error({ runId: input.runId, err }, "Discovery run failed");
@@ -13,6 +13,8 @@ import {
13
13
  DEFAULT_DISCOVERY_PROMOTE_BUCKETS,
14
14
  DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES,
15
15
  DISCOVERY_DEFAULT_DEDUP_THRESHOLD,
16
+ DISCOVERY_DEFAULT_PROBE_CONCURRENCY,
17
+ DISCOVERY_PROBE_CONCURRENCY_CAP,
16
18
  DISCOVERY_PROMOTE_COMPETITOR_CAP,
17
19
  DISCOVERY_PROMOTE_COMPETITOR_MIN_HITS,
18
20
  DiscoveryBuckets,
@@ -171,6 +173,7 @@ import {
171
173
  keywordGenerateRequestSchema,
172
174
  latestProjectRunDtoSchema,
173
175
  locationContextSchema,
176
+ mapWithConcurrency,
174
177
  mentionStateFromAnswerMentioned,
175
178
  missingDependency,
176
179
  normalizeProjectAliases,
@@ -264,7 +267,7 @@ import {
264
267
  wordpressSchemaDeployResultDtoSchema,
265
268
  wordpressSchemaStatusResultDtoSchema,
266
269
  wordpressStatusDtoSchema
267
- } from "./chunk-LZW4ABXX.js";
270
+ } from "./chunk-VOR5ZTUU.js";
268
271
 
269
272
  // src/intelligence-service.ts
270
273
  import { eq as eq38, desc as desc18, asc as asc6, and as and28, ne as ne5, or as or5, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
@@ -1133,6 +1136,11 @@ var discoverySessions = sqliteTable("discovery_sessions", {
1133
1136
  seedProvider: text("seed_provider"),
1134
1137
  seedCountRaw: integer("seed_count_raw"),
1135
1138
  seedCount: integer("seed_count"),
1139
+ // Diagnostics: split of the raw seed candidates by source — the model's
1140
+ // answer text vs. the grounding fan-out (issued search queries). Recorded at
1141
+ // seed time; nullable for legacy sessions. No gate/warning math reads these.
1142
+ seedFromAnswerCount: integer("seed_from_answer_count"),
1143
+ seedFromGroundingCount: integer("seed_from_grounding_count"),
1136
1144
  dedupThreshold: real("dedup_threshold"),
1137
1145
  probeCount: integer("probe_count"),
1138
1146
  citedCount: integer("cited_count"),
@@ -3370,6 +3378,18 @@ var MIGRATION_VERSIONS = [
3370
3378
  `ALTER TABLE api_keys ADD COLUMN project_id TEXT REFERENCES projects(id) ON DELETE CASCADE`,
3371
3379
  `CREATE INDEX IF NOT EXISTS idx_api_keys_project ON api_keys(project_id)`
3372
3380
  ]
3381
+ },
3382
+ {
3383
+ version: 88,
3384
+ name: "discovery-session-seed-source-counts",
3385
+ statements: [
3386
+ // Diagnostics: split of raw seed candidates by source (answer text vs.
3387
+ // grounding fan-out), recorded at seed time. Nullable — legacy sessions
3388
+ // stay null. ALTER ADD COLUMN is idempotent (the runner swallows the
3389
+ // duplicate-column error on retry).
3390
+ `ALTER TABLE discovery_sessions ADD COLUMN seed_from_answer_count INTEGER`,
3391
+ `ALTER TABLE discovery_sessions ADD COLUMN seed_from_grounding_count INTEGER`
3392
+ ]
3373
3393
  }
3374
3394
  ];
3375
3395
  function rebuildBacklinkTableWithSource(tx, table) {
@@ -18025,7 +18045,7 @@ var routeCatalog = [
18025
18045
  method: "post",
18026
18046
  path: "/api/v1/projects/{name}/discover/run",
18027
18047
  summary: "Start a tracked-basket discovery session",
18028
- description: 'Kicks off a discovery session for the project. The pipeline: ICP description \u2192 Gemini grounded seed prompt \u2192 embed + cluster (cosine \u2265 0.95 by default) \u2192 pick canonical representatives \u2192 probe each canonical via Gemini grounding \u2192 classify into cited / aspirational / wasted-surface \u2192 aggregate competitor map. Returns immediately with `{ runId, sessionId, status: "running", consolidated }`; the actual work runs in the background. Poll `GET /projects/{name}/discover/sessions/{id}` until `status` is `completed` or `failed`. Concurrent/duplicate requests for the same (project, ICP) are consolidated onto a single in-flight session: the response carries `consolidated: true` and `200 OK` instead of `201`, and the request\'s `dedupThreshold` / `maxProbes` are ignored (the in-flight session keeps its original config).',
18048
+ description: 'Kicks off a discovery session for the project. The pipeline: ICP description \u2192 Gemini grounded seed prompt \u2192 embed + cluster (cosine \u2265 0.95 by default) \u2192 pick canonical representatives \u2192 probe each canonical via Gemini grounding \u2192 classify into cited / aspirational / wasted-surface \u2192 aggregate competitor map. Returns immediately with `{ runId, sessionId, status: "running", consolidated }`; the actual work runs in the background. Poll `GET /projects/{name}/discover/sessions/{id}` until `status` is `completed` or `failed`. Concurrent/duplicate requests for the same (project, ICP) are consolidated onto a single in-flight session: the response carries `consolidated: true` and `200 OK` instead of `201`, and the request\'s `dedupThreshold` / `maxProbes` / `probeConcurrency` are ignored (the in-flight session keeps its original config).',
18029
18049
  tags: ["discovery"],
18030
18050
  parameters: [nameParameter],
18031
18051
  requestBody: {
@@ -18038,6 +18058,12 @@ var routeCatalog = [
18038
18058
  icpDescription: { type: "string", description: "Free-text ICP. Required if the project does not have spec.icpDescription stored." },
18039
18059
  dedupThreshold: { type: "number", description: "Cosine similarity threshold for clustering. Defaults to 0.95." },
18040
18060
  maxProbes: { type: "integer", description: "Max canonical queries to probe in this session. Default 100, hard cap 500." },
18061
+ probeConcurrency: {
18062
+ type: "integer",
18063
+ minimum: 1,
18064
+ maximum: 8,
18065
+ description: "How many probes may run in parallel. Default 1 (strictly serial \u2014 the historical behaviour), hard cap 8. Probe rows are persisted in canonical order regardless of concurrency, so this only shortens wall-clock time."
18066
+ },
18041
18067
  locations: {
18042
18068
  type: "array",
18043
18069
  items: { type: "string" },
@@ -24244,7 +24270,7 @@ function buildAmbiguousSlugMessage(slug, pages) {
24244
24270
  }).join(", ");
24245
24271
  return `Multiple pages matched slug "${slug}". Candidates: ${candidates}.`;
24246
24272
  }
24247
- async function mapWithConcurrency(items, limit, iteratee) {
24273
+ async function mapWithConcurrency2(items, limit, iteratee) {
24248
24274
  const results = new Array(items.length);
24249
24275
  let nextIndex = 0;
24250
24276
  async function worker() {
@@ -24541,7 +24567,7 @@ function buildManualMetaAssist(siteUrl, slug, link, meta) {
24541
24567
  async function bulkSetSeoMeta(connection, entries, env) {
24542
24568
  const site = resolveEnvironment(connection, env);
24543
24569
  const { strategy, plugins } = await detectSeoWriteStrategy(connection, site.env);
24544
- const results = await mapWithConcurrency(
24570
+ const results = await mapWithConcurrency2(
24545
24571
  entries,
24546
24572
  3,
24547
24573
  async (entry) => {
@@ -24699,7 +24725,7 @@ async function deploySchema(connection, slug, schemas, env) {
24699
24725
  async function deploySchemaFromProfile(connection, profile, env) {
24700
24726
  const site = resolveEnvironment(connection, env);
24701
24727
  const slugEntries = Object.entries(profile.pages);
24702
- const results = await mapWithConcurrency(
24728
+ const results = await mapWithConcurrency2(
24703
24729
  slugEntries,
24704
24730
  3,
24705
24731
  async ([slug, entries]) => {
@@ -24721,7 +24747,7 @@ async function deploySchemaFromProfile(connection, profile, env) {
24721
24747
  async function getSchemaStatus(connection, env) {
24722
24748
  const site = resolveEnvironment(connection, env);
24723
24749
  const pages = await listPages(connection, site.env);
24724
- const details = await mapWithConcurrency(
24750
+ const details = await mapWithConcurrency2(
24725
24751
  pages,
24726
24752
  5,
24727
24753
  async (page) => getPageDetail(connection, page.slug, site.env)
@@ -24822,7 +24848,7 @@ async function runAudit(connection, env) {
24822
24848
  const site = resolveEnvironment(connection, env);
24823
24849
  const pages = await listPages(connection, site.env);
24824
24850
  const plugins = await listActivePlugins(connection, site.env);
24825
- const details = await mapWithConcurrency(
24851
+ const details = await mapWithConcurrency2(
24826
24852
  pages,
24827
24853
  5,
24828
24854
  async (page) => getPageDetail(connection, page.slug, site.env, plugins)
@@ -33792,6 +33818,7 @@ async function discoveryRoutes(app, opts) {
33792
33818
  icpDescription,
33793
33819
  dedupThreshold: parsed.data.dedupThreshold,
33794
33820
  maxProbes: parsed.data.maxProbes,
33821
+ probeConcurrency: parsed.data.probeConcurrency,
33795
33822
  locations
33796
33823
  });
33797
33824
  return reply.status(201).send({
@@ -34041,6 +34068,8 @@ function serializeSession(row) {
34041
34068
  seedProvider: row.seedProvider ?? null,
34042
34069
  seedCountRaw: row.seedCountRaw ?? null,
34043
34070
  seedCount: row.seedCount ?? null,
34071
+ seedFromAnswerCount: row.seedFromAnswerCount ?? null,
34072
+ seedFromGroundingCount: row.seedFromGroundingCount ?? null,
34044
34073
  dedupThreshold: row.dedupThreshold ?? null,
34045
34074
  probeCount: row.probeCount ?? null,
34046
34075
  citedCount: row.citedCount ?? null,
@@ -34134,6 +34163,10 @@ async function executeDiscovery(opts) {
34134
34163
  const dedupThreshold = opts.dedupThreshold ?? DISCOVERY_DEFAULT_DEDUP_THRESHOLD;
34135
34164
  const requestedMax = opts.maxProbes ?? DEFAULT_MAX_PROBES;
34136
34165
  const maxProbes = Math.min(Math.max(1, requestedMax), ABSOLUTE_MAX_PROBES);
34166
+ const probeConcurrency = Math.min(
34167
+ Math.max(1, Math.floor(opts.probeConcurrency ?? DISCOVERY_DEFAULT_PROBE_CONCURRENCY)),
34168
+ DISCOVERY_PROBE_CONCURRENCY_CAP
34169
+ );
34137
34170
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
34138
34171
  opts.db.update(discoverySessions).set({
34139
34172
  status: DiscoverySessionStatuses.seeding,
@@ -34164,12 +34197,21 @@ async function executeDiscovery(opts) {
34164
34197
  seedProvider: seedResult.provider,
34165
34198
  seedCountRaw,
34166
34199
  seedCount,
34200
+ // Seed-source diagnostics (answer text vs. grounding fan-out). Null when
34201
+ // the seed dep does not report the split. Not consumed by any gate.
34202
+ seedFromAnswerCount: seedResult.fromAnswerCount ?? null,
34203
+ seedFromGroundingCount: seedResult.fromGroundingCount ?? null,
34167
34204
  warning
34168
34205
  }).where(eq36(discoverySessions.id, opts.sessionId)).run();
34206
+ const probeResults = await mapWithConcurrency(
34207
+ probedCanonicals,
34208
+ probeConcurrency,
34209
+ (query) => opts.deps.probe({ project: opts.project, query })
34210
+ );
34169
34211
  const probeRows = [];
34170
34212
  const buckets = { cited: 0, aspirational: 0, "wasted-surface": 0 };
34171
- for (const query of probedCanonicals) {
34172
- const probe = await opts.deps.probe({ project: opts.project, query });
34213
+ const insertBaseMs = Date.now();
34214
+ const rowsToInsert = probeResults.map((probe, index2) => {
34173
34215
  const bucket = classifyProbeBucket({
34174
34216
  citationState: probe.citationState,
34175
34217
  citedDomains: probe.citedDomains,
@@ -34177,18 +34219,25 @@ async function executeDiscovery(opts) {
34177
34219
  });
34178
34220
  probeRows.push({ citedDomains: probe.citedDomains, bucket });
34179
34221
  buckets[bucket]++;
34180
- opts.db.insert(discoveryProbes).values({
34222
+ return {
34181
34223
  id: crypto28.randomUUID(),
34182
34224
  sessionId: opts.sessionId,
34183
34225
  projectId: opts.project.id,
34184
- query,
34226
+ query: probedCanonicals[index2],
34185
34227
  bucket,
34186
34228
  citationState: probe.citationState,
34187
34229
  answerMentioned: probe.answerMentioned,
34188
34230
  citedDomains: probe.citedDomains,
34189
34231
  rawResponse: JSON.stringify(probe.rawResponse),
34190
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
34191
- }).run();
34232
+ createdAt: new Date(insertBaseMs + index2).toISOString()
34233
+ };
34234
+ });
34235
+ if (rowsToInsert.length > 0) {
34236
+ opts.db.transaction((tx) => {
34237
+ for (const values of rowsToInsert) {
34238
+ tx.insert(discoveryProbes).values(values).run();
34239
+ }
34240
+ });
34192
34241
  }
34193
34242
  const domains = buildCompetitorMap(probeRows, opts.project).map((entry) => entry.domain);
34194
34243
  const classification = await classifyCompetitorDomains(
@@ -2,6 +2,7 @@ import {
2
2
  AGENT_MEMORY_KEY_MAX_LENGTH,
3
3
  AGENT_MEMORY_VALUE_MAX_BYTES,
4
4
  DISCOVERY_MAX_PROBES_CAP,
5
+ DISCOVERY_PROBE_CONCURRENCY_CAP,
5
6
  backlinkSourceSchema,
6
7
  competitorBatchRequestSchema,
7
8
  discoveryBucketSchema,
@@ -24,7 +25,7 @@ import {
24
25
  trafficConnectVercelRequestSchema,
25
26
  trafficConnectWordpressRequestSchema,
26
27
  trafficEventKindSchema
27
- } from "./chunk-LZW4ABXX.js";
28
+ } from "./chunk-VOR5ZTUU.js";
28
29
 
29
30
  // src/config.ts
30
31
  import fs from "fs";
@@ -5565,7 +5566,8 @@ var discoveryRunInputSchema = z2.object({
5565
5566
  // upper bound; this just clarifies the meaning of each knob.
5566
5567
  icpDescription: z2.string().min(1).optional().describe("Free-text ICP description. If omitted, the project must already have spec.icpDescription stored."),
5567
5568
  dedupThreshold: z2.number().min(0).max(1).optional().describe("Cosine similarity threshold for clustering seed candidates. Defaults to 0.85. Lower values dedupe more aggressively."),
5568
- maxProbes: z2.number().int().positive().max(DISCOVERY_MAX_PROBES_CAP).optional().describe(`Max canonical queries to probe in this session. Default 100, hard cap ${DISCOVERY_MAX_PROBES_CAP}.`)
5569
+ maxProbes: z2.number().int().positive().max(DISCOVERY_MAX_PROBES_CAP).optional().describe(`Max canonical queries to probe in this session. Default 100, hard cap ${DISCOVERY_MAX_PROBES_CAP}.`),
5570
+ probeConcurrency: z2.number().int().min(1).max(DISCOVERY_PROBE_CONCURRENCY_CAP).optional().describe(`How many probes may run in parallel. Default 1 (strictly serial), hard cap ${DISCOVERY_PROBE_CONCURRENCY_CAP}. Probe rows are persisted in canonical order regardless of concurrency, so this only shortens wall-clock time.`)
5569
5571
  }).optional()
5570
5572
  });
5571
5573
  var discoverySessionsListInputSchema = z2.object({
@@ -1188,6 +1188,33 @@ function isRetryableHttpError(err) {
1188
1188
  return true;
1189
1189
  }
1190
1190
 
1191
+ // ../contracts/src/concurrency.ts
1192
+ async function mapWithConcurrency(items, concurrency, fn) {
1193
+ if (items.length === 0) return [];
1194
+ const limit = Math.min(Math.max(1, Math.floor(concurrency)), items.length);
1195
+ const results = new Array(items.length);
1196
+ const state = {
1197
+ nextIndex: 0,
1198
+ firstFailure: null
1199
+ };
1200
+ async function worker() {
1201
+ for (; ; ) {
1202
+ if (state.firstFailure) return;
1203
+ const index = state.nextIndex++;
1204
+ if (index >= items.length) return;
1205
+ try {
1206
+ results[index] = await fn(items[index], index);
1207
+ } catch (err) {
1208
+ state.firstFailure ??= { err };
1209
+ return;
1210
+ }
1211
+ }
1212
+ }
1213
+ await Promise.all(Array.from({ length: limit }, () => worker()));
1214
+ if (state.firstFailure) throw state.firstFailure.err;
1215
+ return results;
1216
+ }
1217
+
1191
1218
  // ../contracts/src/bing.ts
1192
1219
  import { z as z9 } from "zod";
1193
1220
  var bingConnectionDtoSchema = z9.object({
@@ -2150,6 +2177,15 @@ var discoverySessionDtoSchema = z17.object({
2150
2177
  seedProvider: z17.string().nullable().optional(),
2151
2178
  seedCountRaw: z17.number().int().nullable().optional(),
2152
2179
  seedCount: z17.number().int().nullable().optional(),
2180
+ /**
2181
+ * Diagnostics: how many raw seed candidates came from the model's answer
2182
+ * text vs. from the grounding fan-out (the search queries the engine
2183
+ * actually issued). Recorded at seed time so seed-quality calibration is
2184
+ * measurable per session. Null on legacy sessions (or when a seed dep does
2185
+ * not report the split). Purely additive — no gate or warning reads these.
2186
+ */
2187
+ seedFromAnswerCount: z17.number().int().nullable().optional(),
2188
+ seedFromGroundingCount: z17.number().int().nullable().optional(),
2153
2189
  dedupThreshold: z17.number().nullable().optional(),
2154
2190
  probeCount: z17.number().int().nullable().optional(),
2155
2191
  citedCount: z17.number().int().nullable().default(null),
@@ -2171,13 +2207,15 @@ var discoverySessionDetailDtoSchema = discoverySessionDtoSchema.extend({
2171
2207
  probes: z17.array(discoveryProbeDtoSchema).default([])
2172
2208
  });
2173
2209
  var DISCOVERY_MAX_PROBES_CAP = 500;
2210
+ var DISCOVERY_PROBE_CONCURRENCY_CAP = 8;
2211
+ var DISCOVERY_DEFAULT_PROBE_CONCURRENCY = 1;
2174
2212
  var DISCOVERY_DEFAULT_DEDUP_THRESHOLD = 0.95;
2175
2213
  var DISCOVERY_SEED_COLLAPSE_RATIO = 0.2;
2176
2214
  var DISCOVERY_SEED_COLLAPSE_MIN_RAW = 10;
2177
2215
  function seedCollapseWarning(input) {
2178
2216
  const { seedCountRaw, canonicalCount, dedupThreshold } = input;
2179
2217
  if (seedCountRaw < DISCOVERY_SEED_COLLAPSE_MIN_RAW) return null;
2180
- if (canonicalCount / seedCountRaw >= DISCOVERY_SEED_COLLAPSE_RATIO) return null;
2218
+ if (canonicalCount / seedCountRaw > DISCOVERY_SEED_COLLAPSE_RATIO) return null;
2181
2219
  const noun = canonicalCount === 1 ? "query" : "queries";
2182
2220
  return `Seed dedup collapsed ${seedCountRaw} raw candidates into ${canonicalCount} canonical ${noun} at threshold ${dedupThreshold}. Distinct intents were likely merged into one cluster; re-run with a higher --dedup-threshold.`;
2183
2221
  }
@@ -2185,6 +2223,14 @@ var discoveryRunRequestSchema = z17.object({
2185
2223
  icpDescription: z17.string().min(1).optional(),
2186
2224
  dedupThreshold: z17.number().min(0).max(1).optional(),
2187
2225
  maxProbes: z17.number().int().positive().max(DISCOVERY_MAX_PROBES_CAP).optional(),
2226
+ /**
2227
+ * How many probes may run in parallel for this session. Defaults to
2228
+ * `DISCOVERY_DEFAULT_PROBE_CONCURRENCY` (1 — strictly serial, the historical
2229
+ * behaviour) when omitted; capped at `DISCOVERY_PROBE_CONCURRENCY_CAP`.
2230
+ * Probe results are persisted in canonical order regardless of concurrency,
2231
+ * so this only changes wall-clock time, never output order.
2232
+ */
2233
+ probeConcurrency: z17.number().int().min(1).max(DISCOVERY_PROBE_CONCURRENCY_CAP).optional(),
2188
2234
  /**
2189
2235
  * Optional override of the project's location labels, constraining seed
2190
2236
  * generation to a subset of the configured service areas. Each label must
@@ -5531,6 +5577,7 @@ export {
5531
5577
  formatGbpMetricLabel,
5532
5578
  withRetry,
5533
5579
  isRetryableHttpError,
5580
+ mapWithConcurrency,
5534
5581
  bingUrlInspectionDtoSchema,
5535
5582
  bingCoverageSummaryDtoSchema,
5536
5583
  bingKeywordStatsDtoSchema,
@@ -5609,6 +5656,8 @@ export {
5609
5656
  discoverySessionDtoSchema,
5610
5657
  discoverySessionDetailDtoSchema,
5611
5658
  DISCOVERY_MAX_PROBES_CAP,
5659
+ DISCOVERY_PROBE_CONCURRENCY_CAP,
5660
+ DISCOVERY_DEFAULT_PROBE_CONCURRENCY,
5612
5661
  DISCOVERY_DEFAULT_DEDUP_THRESHOLD,
5613
5662
  seedCollapseWarning,
5614
5663
  discoveryRunRequestSchema,
package/dist/cli.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  setTelemetrySource,
30
30
  showFirstRunNotice,
31
31
  trackEvent
32
- } from "./chunk-XRKGLZF6.js";
32
+ } from "./chunk-2AWATO4I.js";
33
33
  import {
34
34
  CliError,
35
35
  EXIT_SYSTEM_ERROR,
@@ -46,7 +46,7 @@ import {
46
46
  saveConfig,
47
47
  saveConfigPatch,
48
48
  usageError
49
- } from "./chunk-IDSJTITA.js";
49
+ } from "./chunk-VGMTLUUM.js";
50
50
  import {
51
51
  apiKeys,
52
52
  createClient,
@@ -54,7 +54,7 @@ import {
54
54
  projects,
55
55
  queries,
56
56
  renderReportHtml
57
- } from "./chunk-UCKUZCIM.js";
57
+ } from "./chunk-F3MKVMS5.js";
58
58
  import {
59
59
  BacklinkSources,
60
60
  CcReleaseSyncStatuses,
@@ -80,7 +80,7 @@ import {
80
80
  providerQuotaPolicySchema,
81
81
  resolveProviderInput,
82
82
  winnabilityClassSchema
83
- } from "./chunk-LZW4ABXX.js";
83
+ } from "./chunk-VOR5ZTUU.js";
84
84
 
85
85
  // src/cli.ts
86
86
  import { pathToFileURL } from "url";
@@ -1657,6 +1657,7 @@ function buildRunBody(opts, icpDescription) {
1657
1657
  if (icpDescription) body.icpDescription = icpDescription;
1658
1658
  if (opts.dedupThreshold !== void 0) body.dedupThreshold = opts.dedupThreshold;
1659
1659
  if (opts.maxProbes !== void 0) body.maxProbes = opts.maxProbes;
1660
+ if (opts.probeConcurrency !== void 0) body.probeConcurrency = opts.probeConcurrency;
1660
1661
  if (opts.locations && opts.locations.length > 0) body.locations = opts.locations;
1661
1662
  return body;
1662
1663
  }
@@ -2056,17 +2057,18 @@ Usage: ${usage}`,
2056
2057
  var DISCOVER_CLI_COMMANDS = [
2057
2058
  {
2058
2059
  path: ["discover", "run"],
2059
- usage: 'canonry discover run <project> [--icp "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--wait] [--format json]',
2060
+ usage: 'canonry discover run <project> [--icp "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]',
2060
2061
  options: {
2061
2062
  icp: stringOption(),
2062
2063
  "icp-angle": multiStringOption(),
2063
2064
  locations: multiStringOption(),
2064
2065
  "dedup-threshold": stringOption(),
2065
2066
  "max-probes": stringOption(),
2067
+ "probe-concurrency": stringOption(),
2066
2068
  wait: { type: "boolean", default: false }
2067
2069
  },
2068
2070
  run: async (input) => {
2069
- const usage = 'canonry discover run <project> [--icp "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--wait] [--format json]';
2071
+ const usage = 'canonry discover run <project> [--icp "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]';
2070
2072
  const project = requireProject(input, "discover.run", usage);
2071
2073
  await discoverRun(project, {
2072
2074
  icp: getString(input.values, "icp"),
@@ -2078,6 +2080,11 @@ var DISCOVER_CLI_COMMANDS = [
2078
2080
  usage,
2079
2081
  message: "--max-probes must be an integer"
2080
2082
  }),
2083
+ probeConcurrency: parseIntegerOption(input, "probe-concurrency", {
2084
+ command: "discover.run",
2085
+ usage,
2086
+ message: "--probe-concurrency must be an integer"
2087
+ }),
2081
2088
  wait: getBoolean(input.values, "wait"),
2082
2089
  format: input.format
2083
2090
  });
@@ -2085,17 +2092,18 @@ var DISCOVER_CLI_COMMANDS = [
2085
2092
  },
2086
2093
  {
2087
2094
  path: ["discover", "seed"],
2088
- usage: 'canonry discover seed <project> [--icp "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--wait] [--format json]',
2095
+ usage: 'canonry discover seed <project> [--icp "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]',
2089
2096
  options: {
2090
2097
  icp: stringOption(),
2091
2098
  "icp-angle": multiStringOption(),
2092
2099
  locations: multiStringOption(),
2093
2100
  "dedup-threshold": stringOption(),
2094
2101
  "max-probes": stringOption(),
2102
+ "probe-concurrency": stringOption(),
2095
2103
  wait: { type: "boolean", default: false }
2096
2104
  },
2097
2105
  run: async (input) => {
2098
- const usage = 'canonry discover seed <project> [--icp "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--wait] [--format json]';
2106
+ const usage = 'canonry discover seed <project> [--icp "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]';
2099
2107
  const project = requireProject(input, "discover.seed", usage);
2100
2108
  await discoverSeed(project, {
2101
2109
  icp: getString(input.values, "icp"),
@@ -2107,6 +2115,11 @@ var DISCOVER_CLI_COMMANDS = [
2107
2115
  usage,
2108
2116
  message: "--max-probes must be an integer"
2109
2117
  }),
2118
+ probeConcurrency: parseIntegerOption(input, "probe-concurrency", {
2119
+ command: "discover.seed",
2120
+ usage,
2121
+ message: "--probe-concurrency must be an integer"
2122
+ }),
2110
2123
  wait: getBoolean(input.values, "wait"),
2111
2124
  format: input.format
2112
2125
  });
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-XRKGLZF6.js";
3
+ } from "./chunk-2AWATO4I.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-IDSJTITA.js";
7
- import "./chunk-UCKUZCIM.js";
8
- import "./chunk-LZW4ABXX.js";
6
+ } from "./chunk-VGMTLUUM.js";
7
+ import "./chunk-F3MKVMS5.js";
8
+ import "./chunk-VOR5ZTUU.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-UCKUZCIM.js";
4
- import "./chunk-LZW4ABXX.js";
3
+ } from "./chunk-F3MKVMS5.js";
4
+ import "./chunk-VOR5ZTUU.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -3,10 +3,10 @@ import {
3
3
  PACKAGE_VERSION,
4
4
  canonryMcpTools,
5
5
  createApiClient
6
- } from "./chunk-IDSJTITA.js";
6
+ } from "./chunk-VGMTLUUM.js";
7
7
  import {
8
8
  isReadOnlyKey
9
- } from "./chunk-LZW4ABXX.js";
9
+ } from "./chunk-VOR5ZTUU.js";
10
10
 
11
11
  // src/mcp/cli.ts
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "4.105.0",
3
+ "version": "4.106.1",
4
4
  "type": "module",
5
5
  "description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -66,26 +66,26 @@
66
66
  "tsup": "^8.5.1",
67
67
  "tsx": "^4.19.0",
68
68
  "@ainyc/canonry-api-client": "0.0.0",
69
+ "@ainyc/canonry-api-routes": "0.0.0",
69
70
  "@ainyc/canonry-config": "0.0.0",
70
71
  "@ainyc/canonry-contracts": "0.0.0",
71
- "@ainyc/canonry-api-routes": "0.0.0",
72
+ "@ainyc/canonry-db": "0.0.0",
72
73
  "@ainyc/canonry-integration-bing": "0.0.0",
73
74
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
74
- "@ainyc/canonry-db": "0.0.0",
75
- "@ainyc/canonry-integration-commoncrawl": "0.0.0",
76
- "@ainyc/canonry-integration-google": "0.0.0",
75
+ "@ainyc/canonry-integration-cloud-run": "0.0.0",
77
76
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
77
+ "@ainyc/canonry-integration-google": "0.0.0",
78
+ "@ainyc/canonry-integration-commoncrawl": "0.0.0",
78
79
  "@ainyc/canonry-integration-google-places": "0.0.0",
79
- "@ainyc/canonry-integration-cloud-run": "0.0.0",
80
- "@ainyc/canonry-integration-traffic": "0.0.0",
80
+ "@ainyc/canonry-intelligence": "0.0.0",
81
81
  "@ainyc/canonry-integration-wordpress": "0.0.0",
82
82
  "@ainyc/canonry-provider-cdp": "0.0.0",
83
- "@ainyc/canonry-intelligence": "0.0.0",
83
+ "@ainyc/canonry-integration-traffic": "0.0.0",
84
84
  "@ainyc/canonry-provider-claude": "0.0.0",
85
- "@ainyc/canonry-provider-gemini": "0.0.0",
86
85
  "@ainyc/canonry-provider-local": "0.0.0",
86
+ "@ainyc/canonry-provider-openai": "0.0.0",
87
87
  "@ainyc/canonry-provider-perplexity": "0.0.0",
88
- "@ainyc/canonry-provider-openai": "0.0.0"
88
+ "@ainyc/canonry-provider-gemini": "0.0.0"
89
89
  },
90
90
  "scripts": {
91
91
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",