@ainyc/canonry 4.55.3 → 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.
Files changed (30) hide show
  1. package/assets/agent-workspace/skills/aero/SKILL.md +2 -0
  2. package/assets/agent-workspace/skills/canonry/SKILL.md +2 -0
  3. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +46 -3
  4. package/assets/assets/{BacklinksPage-buvZ4ZOd.js → BacklinksPage-yWx_BBLG.js} +1 -1
  5. package/assets/assets/ChartPrimitives-gqioJ07n.js +1 -0
  6. package/assets/assets/ProjectPage-D1tnXJ2L.js +6 -0
  7. package/assets/assets/{RunRow-D-DTu1PA.js → RunRow-DEr_yQLw.js} +1 -1
  8. package/assets/assets/{RunsPage-CrBpgwkO.js → RunsPage-DMrl5Fhn.js} +1 -1
  9. package/assets/assets/{SettingsPage-Bgsi9tZ2.js → SettingsPage-Dp0TXf34.js} +1 -1
  10. package/assets/assets/{TrafficPage-DAHXrzqz.js → TrafficPage-POy4iHHt.js} +1 -1
  11. package/assets/assets/TrafficSourceDetailPage-BewjZ53n.js +1 -0
  12. package/assets/assets/{extract-error-message-BGhWiJPr.js → extract-error-message-C0GGpK4T.js} +1 -1
  13. package/assets/assets/{index-CbDkoDBH.js → index-D0A-UvNH.js} +79 -79
  14. package/assets/assets/index-_jdnW4nh.css +1 -0
  15. package/assets/assets/{server-traffic-3xxyOEIX.js → server-traffic-B5rtrB-q.js} +1 -1
  16. package/assets/assets/{trash-2-dppRdHYI.js → trash-2-CUczQ2Yl.js} +1 -1
  17. package/assets/index.html +2 -2
  18. package/dist/{chunk-XB6Y63NI.js → chunk-4KWPOVIT.js} +1 -1
  19. package/dist/{chunk-UOQ62KDD.js → chunk-6X5TF73A.js} +42 -1
  20. package/dist/{chunk-5EAGNVCJ.js → chunk-I2LAM5IM.js} +207 -63
  21. package/dist/{chunk-XHU35P3S.js → chunk-WFVUZVJD.js} +9 -0
  22. package/dist/cli.js +58 -5
  23. package/dist/index.js +4 -4
  24. package/dist/{intelligence-service-4PT22FED.js → intelligence-service-NY3MAVPB.js} +2 -2
  25. package/dist/mcp.js +2 -2
  26. package/package.json +10 -10
  27. package/assets/assets/ChartPrimitives-9Kx3gzQL.js +0 -1
  28. package/assets/assets/ProjectPage-D0UqSqe7.js +0 -6
  29. package/assets/assets/TrafficSourceDetailPage-DCcDN3VD.js +0 -1
  30. package/assets/assets/index-dxdJhCQO.css +0 -1
@@ -6,7 +6,7 @@ import {
6
6
  loadConfig,
7
7
  loadConfigRaw,
8
8
  saveConfigPatch
9
- } from "./chunk-UOQ62KDD.js";
9
+ } from "./chunk-6X5TF73A.js";
10
10
  import {
11
11
  DEFAULT_RUN_HISTORY_LIMIT,
12
12
  IntelligenceService,
@@ -87,7 +87,7 @@ import {
87
87
  smoothedRunDelta,
88
88
  trafficSources,
89
89
  usageCounters
90
- } from "./chunk-XB6Y63NI.js";
90
+ } from "./chunk-4KWPOVIT.js";
91
91
  import {
92
92
  AGENT_MEMORY_VALUE_MAX_BYTES,
93
93
  AGENT_PROVIDER_IDS,
@@ -267,6 +267,7 @@ import {
267
267
  trafficConnectVercelRequestSchema,
268
268
  trafficConnectWordpressRequestSchema,
269
269
  trafficEventsResponseSchema,
270
+ trafficResetRequestSchema,
270
271
  trafficSourceDetailDtoSchema,
271
272
  trafficSourceDtoSchema,
272
273
  trafficSourceListResponseSchema,
@@ -288,7 +289,7 @@ import {
288
289
  wordpressSchemaDeployResultDtoSchema,
289
290
  wordpressSchemaStatusResultDtoSchema,
290
291
  wordpressStatusDtoSchema
291
- } from "./chunk-XHU35P3S.js";
292
+ } from "./chunk-WFVUZVJD.js";
292
293
 
293
294
  // src/telemetry.ts
294
295
  import crypto from "crypto";
@@ -11502,6 +11503,36 @@ var routeCatalog = [
11502
11503
  404: errorResponse("Project or traffic source not found.")
11503
11504
  }
11504
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
+ },
11505
11536
  {
11506
11537
  method: "get",
11507
11538
  path: "/api/v1/projects/{name}/traffic/sources",
@@ -21590,14 +21621,34 @@ var VERCEL_REQUEST_LOGS_URL = "https://vercel.com/api/logs/request-logs";
21590
21621
  var DEFAULT_ENVIRONMENT = "production";
21591
21622
  var DEFAULT_MAX_PAGES3 = 1;
21592
21623
  var DEFAULT_TIMEOUT_MS3 = 3e4;
21624
+ var DEFAULT_MAX_RETRIES = 3;
21625
+ var DEFAULT_INITIAL_RETRY_DELAY_MS = 1e3;
21593
21626
  var VercelLogsApiError = class extends Error {
21594
- constructor(message, status, body) {
21627
+ constructor(message, status, body, retryAfterSeconds) {
21595
21628
  super(message);
21596
21629
  this.status = status;
21597
21630
  this.body = body;
21631
+ this.retryAfterSeconds = retryAfterSeconds;
21598
21632
  this.name = "VercelLogsApiError";
21599
21633
  }
21600
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
+ }
21601
21652
  function trimRequired2(name, value) {
21602
21653
  const trimmed = value.trim();
21603
21654
  if (!trimmed) {
@@ -21624,6 +21675,24 @@ async function readErrorBody3(response) {
21624
21675
  if (!text) return void 0;
21625
21676
  return text.length <= 500 ? text : `${text.slice(0, 500)}... [truncated]`;
21626
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
+ }
21627
21696
  async function listVercelTrafficEvents(options) {
21628
21697
  const token = trimRequired2("token", options.token);
21629
21698
  const projectId = trimRequired2("projectId", options.projectId);
@@ -21633,6 +21702,8 @@ async function listVercelTrafficEvents(options) {
21633
21702
  const endDate = toEpochMs("endDate", options.endDate);
21634
21703
  const maxPages = normalizeMaxPages3(options.maxPages);
21635
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;
21636
21707
  let rawEntryCount = 0;
21637
21708
  let skippedEntryCount = 0;
21638
21709
  let hasMore = false;
@@ -21646,23 +21717,27 @@ async function listVercelTrafficEvents(options) {
21646
21717
  url.searchParams.set("startDate", startDate);
21647
21718
  url.searchParams.set("endDate", endDate);
21648
21719
  url.searchParams.set("environment", environment);
21649
- const response = await fetch(url, {
21650
- method: "GET",
21651
- headers: {
21652
- Authorization: `Bearer ${token}`,
21653
- Accept: "application/json"
21654
- },
21655
- signal: AbortSignal.timeout(timeoutMs)
21656
- });
21657
- if (!response.ok) {
21658
- const body2 = await readErrorBody3(response);
21659
- throw new VercelLogsApiError(
21660
- `Vercel request-logs endpoint returned HTTP ${response.status}`,
21661
- response.status,
21662
- body2
21663
- );
21664
- }
21665
- const body = await response.json();
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);
21666
21741
  const rows = body.rows ?? [];
21667
21742
  rawEntryCount += rows.length;
21668
21743
  for (const row of rows) {
@@ -21686,8 +21761,10 @@ async function listVercelTrafficEvents(options) {
21686
21761
  }
21687
21762
 
21688
21763
  // ../integration-vercel/src/drain.ts
21689
- var MIN_SUB_WINDOW_MS = 6e4;
21764
+ var MIN_SUB_WINDOW_MS = 1e3;
21690
21765
  var FLOOR_SLICE_MAX_PAGES = 1e3;
21766
+ var FLOOR_CONGESTION_PROBE_INTERVAL = 60;
21767
+ var RETENTION_PROBE_WINDOW_MS = 6e4;
21691
21768
  var RETENTION_BOUNDARY_TOLERANCE_MS = 60 * 6e4;
21692
21769
  function toMs(value) {
21693
21770
  return typeof value === "number" ? value : value.getTime();
@@ -21713,7 +21790,7 @@ async function isServable(options, windowStartMs, windowEndMs) {
21713
21790
  }
21714
21791
  }
21715
21792
  async function resolveRetainedStart(options, unservableStartMs, endMs) {
21716
- const tailStartMs = Math.max(unservableStartMs, endMs - MIN_SUB_WINDOW_MS);
21793
+ const tailStartMs = Math.max(unservableStartMs, endMs - RETENTION_PROBE_WINDOW_MS);
21717
21794
  if (!await isServable(options, tailStartMs, endMs)) {
21718
21795
  return endMs;
21719
21796
  }
@@ -21743,6 +21820,8 @@ async function drainVercelTrafficEvents(options) {
21743
21820
  let effectiveStartMs = startMs;
21744
21821
  let retentionClamped = false;
21745
21822
  let retentionResolved = false;
21823
+ let floorSpanProbeCountdown = 0;
21824
+ let floorPageBudgetCountdown = 0;
21746
21825
  while (cursorMs < endMs) {
21747
21826
  if (subWindowCount >= options.maxSubWindows) {
21748
21827
  throw new Error(
@@ -21750,6 +21829,9 @@ async function drainVercelTrafficEvents(options) {
21750
21829
  );
21751
21830
  }
21752
21831
  const subEndMs = Math.min(cursorMs + spanMs, endMs);
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;
21753
21835
  let page;
21754
21836
  try {
21755
21837
  page = await options.pull({
@@ -21759,7 +21841,7 @@ async function drainVercelTrafficEvents(options) {
21759
21841
  environment: options.environment,
21760
21842
  startDate: cursorMs,
21761
21843
  endDate: subEndMs,
21762
- maxPages: options.pagesPerSubWindow
21844
+ maxPages: pageBudget
21763
21845
  });
21764
21846
  } catch (error) {
21765
21847
  if (isRetentionError(error) && !retentionResolved) {
@@ -21775,11 +21857,18 @@ async function drainVercelTrafficEvents(options) {
21775
21857
  }
21776
21858
  subWindowCount += 1;
21777
21859
  if (page.hasMore) {
21778
- const subSpanMs = subEndMs - cursorMs;
21779
21860
  if (subSpanMs > MIN_SUB_WINDOW_MS) {
21780
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
+ }
21781
21865
  continue;
21782
21866
  }
21867
+ if (pageBudget >= FLOOR_SLICE_MAX_PAGES) {
21868
+ throw new Error(
21869
+ `Vercel ${MIN_SUB_WINDOW_MS / 1e3}-second slice holds more than ${FLOOR_SLICE_MAX_PAGES} pages and cannot be drained further`
21870
+ );
21871
+ }
21783
21872
  page = await options.pull({
21784
21873
  token: options.token,
21785
21874
  projectId: options.projectId,
@@ -21790,9 +21879,11 @@ async function drainVercelTrafficEvents(options) {
21790
21879
  maxPages: FLOOR_SLICE_MAX_PAGES
21791
21880
  });
21792
21881
  subWindowCount += 1;
21882
+ floorSpanProbeCountdown = FLOOR_CONGESTION_PROBE_INTERVAL;
21883
+ floorPageBudgetCountdown = FLOOR_CONGESTION_PROBE_INTERVAL;
21793
21884
  if (page.hasMore) {
21794
21885
  throw new Error(
21795
- `Vercel ${MIN_SUB_WINDOW_MS / 6e4}-minute slice holds more than ${FLOOR_SLICE_MAX_PAGES} pages and cannot be drained further`
21886
+ `Vercel ${MIN_SUB_WINDOW_MS / 1e3}-second slice holds more than ${FLOOR_SLICE_MAX_PAGES} pages and cannot be drained further`
21796
21887
  );
21797
21888
  }
21798
21889
  }
@@ -21805,7 +21896,16 @@ async function drainVercelTrafficEvents(options) {
21805
21896
  cursorMs = subEndMs;
21806
21897
  const remainingMs = endMs - cursorMs;
21807
21898
  if (remainingMs > 0) {
21808
- spanMs = Math.min(spanMs * 2, remainingMs);
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
+ }
21809
21909
  }
21810
21910
  }
21811
21911
  return { events, subWindowCount, effectiveStartMs, retentionClamped };
@@ -21820,6 +21920,7 @@ var DEFAULT_WP_PAGE_SIZE = 500;
21820
21920
  var DEFAULT_WP_MAX_PAGES = 20;
21821
21921
  var DEFAULT_VERCEL_MAX_PAGES = 50;
21822
21922
  var VERCEL_MAX_SUB_WINDOWS = 5e3;
21923
+ var VERCEL_BACKFILL_CHUNK_MS = 60 * 6e4;
21823
21924
  var MAX_TRACKED_EVENT_IDS = 1e3;
21824
21925
  var DEFAULT_BACKFILL_DAYS = 30;
21825
21926
  var MAX_BACKFILL_DAYS = 90;
@@ -21855,6 +21956,11 @@ async function defaultResolveAccessToken(record) {
21855
21956
  "OAuth-mode Cloud Run sync is not yet supported in v1. Provide a service-account key file."
21856
21957
  );
21857
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
+ }
21858
21964
  async function runBackfillTask(options) {
21859
21965
  const {
21860
21966
  app,
@@ -22285,7 +22391,15 @@ async function trafficRoutes(app, opts) {
22285
22391
  sourceType: TrafficSourceTypes.vercel,
22286
22392
  displayName: fallbackName,
22287
22393
  status: TrafficSourceStatuses.connected,
22288
- lastSyncedAt: null,
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,
22289
22403
  lastCursor: null,
22290
22404
  lastError: null,
22291
22405
  archivedAt: null,
@@ -22495,14 +22609,7 @@ async function trafficRoutes(app, opts) {
22495
22609
  maxSubWindows: VERCEL_MAX_SUB_WINDOWS
22496
22610
  });
22497
22611
  if (drained.retentionClamped) {
22498
- app.log.warn(
22499
- {
22500
- sourceId: sourceRow.id,
22501
- requestedStart: windowStart.toISOString(),
22502
- servedStart: new Date(drained.effectiveStartMs).toISOString()
22503
- },
22504
- "Vercel request-logs retention clamp: sync window predated plan retention; ingested only the portion Vercel still serves"
22505
- );
22612
+ throw vercelRetentionClampError(windowStart.getTime(), drained.effectiveStartMs);
22506
22613
  }
22507
22614
  allEvents = drained.events;
22508
22615
  } catch (e) {
@@ -22850,33 +22957,32 @@ async function trafficRoutes(app, opts) {
22850
22957
  const vercelEnvironment = config.environment ?? credential.environment;
22851
22958
  pullErrorPrefix = "Vercel pull failed";
22852
22959
  pullForBackfill = async () => {
22853
- const drained = await drainVercelTrafficEvents({
22854
- pull: pullVercelEvents,
22855
- token: credential.token,
22856
- projectId: vercelProjectId,
22857
- teamId: vercelTeamId,
22858
- environment: vercelEnvironment,
22859
- startDate: windowStart.getTime(),
22860
- endDate: windowEnd.getTime(),
22861
- pagesPerSubWindow: vercelMaxPages,
22862
- maxSubWindows: VERCEL_MAX_SUB_WINDOWS
22863
- });
22864
- if (drained.retentionClamped) {
22865
- const servedDays = Math.round(
22866
- (windowEnd.getTime() - drained.effectiveStartMs) / 864e5
22867
- );
22868
- app.log.warn(
22869
- {
22870
- sourceId: sourceRow.id,
22871
- requestedDays,
22872
- servedDays,
22873
- requestedStart: windowStart.toISOString(),
22874
- servedStart: new Date(drained.effectiveStartMs).toISOString()
22875
- },
22876
- "Vercel request-logs retention clamp: backfill window predates plan retention; ingested only the portion Vercel still serves"
22877
- );
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
+ }
22878
22984
  }
22879
- return drained.events;
22985
+ return collected;
22880
22986
  };
22881
22987
  }
22882
22988
  const startedAt = windowEnd.toISOString();
@@ -22962,6 +23068,43 @@ async function trafficRoutes(app, opts) {
22962
23068
  } : null
22963
23069
  };
22964
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
+ });
22965
23108
  app.get("/projects/:name/traffic/sources", async (request) => {
22966
23109
  const project = resolveProject(app.db, request.params.name);
22967
23110
  const rows = app.db.select().from(trafficSources).where(eq24(trafficSources.projectId, project.id)).orderBy(desc13(trafficSources.createdAt)).all();
@@ -30006,7 +30149,7 @@ function readStoredGroundingSources(rawResponse) {
30006
30149
  return result;
30007
30150
  }
30008
30151
  async function backfillInsightsCommand(project, opts) {
30009
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-4PT22FED.js");
30152
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-NY3MAVPB.js");
30010
30153
  const config = loadConfig();
30011
30154
  const db = createClient(config.database);
30012
30155
  migrate(db);
@@ -33408,6 +33551,7 @@ async function createServer(opts) {
33408
33551
  keyUrl: adapter.keyUrl,
33409
33552
  modelHint: `e.g. ${adapter.modelRegistry.defaultModel}`,
33410
33553
  model: registry.get(adapter.name)?.config.model,
33554
+ defaultModel: adapter.modelRegistry.defaultModel,
33411
33555
  configured: !!registry.get(adapter.name),
33412
33556
  quota: registry.get(adapter.name)?.config.quotaPolicy,
33413
33557
  vertexConfigured: adapter.name === "gemini" ? !!opts.config.providers?.gemini?.vertexProject : void 0
@@ -1116,6 +1116,11 @@ var providerSummaryEntryDtoSchema = z10.object({
1116
1116
  keyUrl: z10.string().optional(),
1117
1117
  modelHint: z10.string().optional(),
1118
1118
  model: z10.string().optional(),
1119
+ /**
1120
+ * The adapter's built-in default model. Surfaced so the UI and CLI can show
1121
+ * the effective model even when no explicit `model` override is configured.
1122
+ */
1123
+ defaultModel: z10.string().optional(),
1119
1124
  configured: z10.boolean(),
1120
1125
  quota: providerQuotaPolicySchema.optional(),
1121
1126
  /** Whether Vertex AI is configured for this provider (Gemini only). */
@@ -3225,6 +3230,9 @@ var trafficBackfillRequestSchema = z23.object({
3225
3230
  /** Lookback window in days. Capped server-side at the upstream log retention ceiling (Cloud Logging _Default = 30d). Default: 30. */
3226
3231
  days: z23.number().int().positive().optional()
3227
3232
  });
3233
+ var trafficResetRequestSchema = z23.object({
3234
+ advanceToNow: z23.literal(true)
3235
+ });
3228
3236
  var trafficBackfillResponseSchema = z23.object({
3229
3237
  sourceId: z23.string(),
3230
3238
  runId: z23.string(),
@@ -3773,6 +3781,7 @@ export {
3773
3781
  trafficConnectWordpressRequestSchema,
3774
3782
  trafficConnectVercelRequestSchema,
3775
3783
  trafficSyncResponseSchema,
3784
+ trafficResetRequestSchema,
3776
3785
  trafficBackfillResponseSchema,
3777
3786
  trafficSourceListResponseSchema,
3778
3787
  trafficSourceDetailDtoSchema,
package/dist/cli.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  setTelemetrySource,
24
24
  showFirstRunNotice,
25
25
  trackEvent
26
- } from "./chunk-5EAGNVCJ.js";
26
+ } from "./chunk-I2LAM5IM.js";
27
27
  import {
28
28
  CliError,
29
29
  EXIT_SYSTEM_ERROR,
@@ -39,14 +39,14 @@ import {
39
39
  saveConfig,
40
40
  saveConfigPatch,
41
41
  usageError
42
- } from "./chunk-UOQ62KDD.js";
42
+ } from "./chunk-6X5TF73A.js";
43
43
  import {
44
44
  apiKeys,
45
45
  createClient,
46
46
  migrate,
47
47
  projects,
48
48
  queries
49
- } from "./chunk-XB6Y63NI.js";
49
+ } from "./chunk-4KWPOVIT.js";
50
50
  import {
51
51
  CcReleaseSyncStatuses,
52
52
  CheckScopes,
@@ -65,7 +65,7 @@ import {
65
65
  providerQuotaPolicySchema,
66
66
  resolveProviderInput,
67
67
  skillsClientSchema
68
- } from "./chunk-XHU35P3S.js";
68
+ } from "./chunk-WFVUZVJD.js";
69
69
 
70
70
  // src/cli.ts
71
71
  import { pathToFileURL } from "url";
@@ -3248,6 +3248,36 @@ async function trafficSync(project, opts) {
3248
3248
  console.log(` Sample rows: ${result.sampleRows}`);
3249
3249
  console.log(` Synced at: ${result.syncedAt}`);
3250
3250
  }
3251
+ async function trafficReset(project, opts) {
3252
+ if (!opts.source) {
3253
+ throw new CliError({
3254
+ code: "TRAFFIC_SOURCE_REQUIRED",
3255
+ message: "--source <id> is required",
3256
+ displayMessage: "Error: --source <id> is required (run `canonry traffic sources` to list connected sources)",
3257
+ details: { project }
3258
+ });
3259
+ }
3260
+ if (!opts.advanceToNow) {
3261
+ throw new CliError({
3262
+ code: "TRAFFIC_RESET_REQUIRES_FLAG",
3263
+ message: "--advance-to-now is required",
3264
+ displayMessage: "Error: --advance-to-now is required. This skips any history between the source's current lastSyncedAt and now; run `canonry traffic backfill` separately if you need to recover it.",
3265
+ details: { project, source: opts.source }
3266
+ });
3267
+ }
3268
+ const client = getClient6();
3269
+ const updated = await client.trafficReset(project, opts.source);
3270
+ if (opts.format === "json") {
3271
+ console.log(JSON.stringify(updated, null, 2));
3272
+ return;
3273
+ }
3274
+ console.log(`Traffic source reset for "${project}" (source ${opts.source}).`);
3275
+ console.log(` Status: ${updated.status}`);
3276
+ console.log(` Last synced: ${updated.lastSyncedAt ?? "never"} (advanced to NOW)`);
3277
+ console.log(` Last error: ${updated.lastError ?? "none"}`);
3278
+ console.log("");
3279
+ console.log("Next scheduled sync will resume from this timestamp.");
3280
+ }
3251
3281
  function formatSourceLine(source) {
3252
3282
  const parts = [
3253
3283
  source.id,
@@ -3545,6 +3575,28 @@ var TRAFFIC_CLI_COMMANDS = [
3545
3575
  });
3546
3576
  }
3547
3577
  },
3578
+ {
3579
+ path: ["traffic", "reset"],
3580
+ usage: "canonry traffic reset <project> --source <id> --advance-to-now [--format json]",
3581
+ options: {
3582
+ source: stringOption(),
3583
+ "advance-to-now": { type: "boolean" }
3584
+ },
3585
+ run: async (input) => {
3586
+ const project = requireProject(
3587
+ input,
3588
+ "traffic.reset",
3589
+ "canonry traffic reset <project> --source <id> --advance-to-now"
3590
+ );
3591
+ const source = getString(input.values, "source");
3592
+ if (!source) throw new Error("--source <id> is required");
3593
+ await trafficReset(project, {
3594
+ source,
3595
+ advanceToNow: getBoolean(input.values, "advance-to-now"),
3596
+ format: input.format
3597
+ });
3598
+ }
3599
+ },
3548
3600
  {
3549
3601
  path: ["traffic", "sources"],
3550
3602
  usage: "canonry traffic sources <project> [--format json]",
@@ -7197,7 +7249,8 @@ async function showSettings(format) {
7197
7249
  const status = provider.configured ? "configured" : "not configured";
7198
7250
  console.log(` ${provider.name.padEnd(10)} ${status}`);
7199
7251
  if (provider.configured) {
7200
- console.log(` Model: ${provider.model ?? "(default)"}`);
7252
+ const modelLabel = provider.model ? provider.model : provider.defaultModel ? `${provider.defaultModel} (default)` : "(default)";
7253
+ console.log(` Model: ${modelLabel}`);
7201
7254
  if (provider.quota) {
7202
7255
  console.log(` Quota: ${provider.quota.maxConcurrency} concurrent \xB7 ${provider.quota.maxRequestsPerMinute}/min \xB7 ${provider.quota.maxRequestsPerDay}/day`);
7203
7256
  }
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-5EAGNVCJ.js";
3
+ } from "./chunk-I2LAM5IM.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-UOQ62KDD.js";
7
- import "./chunk-XB6Y63NI.js";
8
- import "./chunk-XHU35P3S.js";
6
+ } from "./chunk-6X5TF73A.js";
7
+ import "./chunk-4KWPOVIT.js";
8
+ import "./chunk-WFVUZVJD.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-XB6Y63NI.js";
4
- import "./chunk-XHU35P3S.js";
3
+ } from "./chunk-4KWPOVIT.js";
4
+ import "./chunk-WFVUZVJD.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  CliError,
3
3
  canonryMcpTools,
4
4
  createApiClient
5
- } from "./chunk-UOQ62KDD.js";
6
- import "./chunk-XHU35P3S.js";
5
+ } from "./chunk-6X5TF73A.js";
6
+ import "./chunk-WFVUZVJD.js";
7
7
 
8
8
  // src/mcp/cli.ts
9
9
  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.55.3",
3
+ "version": "4.56.0",
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",
@@ -61,24 +61,24 @@
61
61
  "@types/node-cron": "^3.0.11",
62
62
  "tsup": "^8.5.1",
63
63
  "tsx": "^4.19.0",
64
- "@ainyc/canonry-api-client": "0.0.0",
64
+ "@ainyc/canonry-api-routes": "0.0.0",
65
65
  "@ainyc/canonry-config": "0.0.0",
66
- "@ainyc/canonry-contracts": "0.0.0",
66
+ "@ainyc/canonry-api-client": "0.0.0",
67
67
  "@ainyc/canonry-db": "0.0.0",
68
+ "@ainyc/canonry-contracts": "0.0.0",
68
69
  "@ainyc/canonry-integration-bing": "0.0.0",
69
- "@ainyc/canonry-api-routes": "0.0.0",
70
- "@ainyc/canonry-integration-cloud-run": "0.0.0",
71
70
  "@ainyc/canonry-integration-traffic": "0.0.0",
72
- "@ainyc/canonry-integration-commoncrawl": "0.0.0",
71
+ "@ainyc/canonry-integration-cloud-run": "0.0.0",
73
72
  "@ainyc/canonry-intelligence": "0.0.0",
74
- "@ainyc/canonry-integration-google": "0.0.0",
75
73
  "@ainyc/canonry-provider-cdp": "0.0.0",
76
74
  "@ainyc/canonry-provider-claude": "0.0.0",
77
- "@ainyc/canonry-integration-wordpress": "0.0.0",
78
75
  "@ainyc/canonry-provider-gemini": "0.0.0",
79
- "@ainyc/canonry-provider-local": "0.0.0",
80
76
  "@ainyc/canonry-provider-openai": "0.0.0",
81
- "@ainyc/canonry-provider-perplexity": "0.0.0"
77
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
78
+ "@ainyc/canonry-provider-local": "0.0.0",
79
+ "@ainyc/canonry-integration-google": "0.0.0",
80
+ "@ainyc/canonry-integration-commoncrawl": "0.0.0",
81
+ "@ainyc/canonry-integration-wordpress": "0.0.0"
82
82
  },
83
83
  "scripts": {
84
84
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",