@ainyc/canonry 4.25.0 → 4.26.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/README.md +1 -1
- package/assets/agent-workspace/skills/canonry-setup/references/canonry-cli.md +1 -0
- package/assets/assets/{index-C4scWriC.js → index-X1r0qycv.js} +14 -14
- package/assets/index.html +1 -1
- package/dist/{chunk-A7HQ6X43.js → chunk-2FAEQ56I.js} +2 -2
- package/dist/{chunk-6J6WQOGH.js → chunk-H4RE4WLW.js} +229 -114
- package/dist/{chunk-CRQMGNPH.js → chunk-HVW665A4.js} +2 -0
- package/dist/{chunk-IS65IYNZ.js → chunk-PN24DAGC.js} +13 -1
- package/dist/cli.js +21 -7
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-XLUYTE57.js → intelligence-service-VUBODIGG.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +7 -7
package/assets/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
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-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-X1r0qycv.js"></script>
|
|
16
16
|
<link rel="stylesheet" crossorigin href="./assets/index-rPok6yk8.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
trafficConnectCloudRunRequestSchema,
|
|
19
19
|
trafficConnectWordpressRequestSchema,
|
|
20
20
|
trafficEventKindSchema
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-HVW665A4.js";
|
|
22
22
|
|
|
23
23
|
// src/config.ts
|
|
24
24
|
import fs from "fs";
|
|
@@ -1954,7 +1954,7 @@ var canonryMcpTools = [
|
|
|
1954
1954
|
defineTool({
|
|
1955
1955
|
name: "canonry_run_trigger",
|
|
1956
1956
|
title: "Trigger run",
|
|
1957
|
-
description: "Trigger an answer-visibility run for a Canonry project.",
|
|
1957
|
+
description: "Trigger an answer-visibility run for a Canonry project. Pass request.queries[] to scope the sweep to a subset of the project's tracked queries; omit for a full sweep.",
|
|
1958
1958
|
access: "write",
|
|
1959
1959
|
tier: "core",
|
|
1960
1960
|
inputSchema: runTriggerInputSchema,
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
loadConfig,
|
|
6
6
|
loadConfigRaw,
|
|
7
7
|
saveConfigPatch
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-2FAEQ56I.js";
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_RUN_HISTORY_LIMIT,
|
|
11
11
|
IntelligenceService,
|
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
schedules,
|
|
69
69
|
trafficSources,
|
|
70
70
|
usageCounters
|
|
71
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-PN24DAGC.js";
|
|
72
72
|
import {
|
|
73
73
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
74
74
|
AGENT_PROVIDER_IDS,
|
|
@@ -169,7 +169,7 @@ import {
|
|
|
169
169
|
visibilityStateFromAnswerMentioned,
|
|
170
170
|
windowCutoff,
|
|
171
171
|
wordpressEnvSchema
|
|
172
|
-
} from "./chunk-
|
|
172
|
+
} from "./chunk-HVW665A4.js";
|
|
173
173
|
|
|
174
174
|
// src/telemetry.ts
|
|
175
175
|
import crypto from "crypto";
|
|
@@ -1155,6 +1155,7 @@ function queueRunIfProjectIdle(db, params) {
|
|
|
1155
1155
|
status: "queued",
|
|
1156
1156
|
trigger,
|
|
1157
1157
|
location: params.location ?? null,
|
|
1158
|
+
queries: params.queries ?? null,
|
|
1158
1159
|
createdAt
|
|
1159
1160
|
}).run();
|
|
1160
1161
|
return { conflict: false, runId };
|
|
@@ -1185,6 +1186,20 @@ async function runRoutes(app, opts) {
|
|
|
1185
1186
|
rawProviders.splice(0, rawProviders.length, ...normalized);
|
|
1186
1187
|
}
|
|
1187
1188
|
const providers = rawProviders?.length ? rawProviders : void 0;
|
|
1189
|
+
let scopedQueries = null;
|
|
1190
|
+
if (body.queries?.length) {
|
|
1191
|
+
const trackedRows = app.db.select({ query: queries.query }).from(queries).where(eq7(queries.projectId, project.id)).all();
|
|
1192
|
+
const tracked = new Set(trackedRows.map((r) => r.query));
|
|
1193
|
+
const missing = body.queries.filter((q) => !tracked.has(q));
|
|
1194
|
+
if (missing.length) {
|
|
1195
|
+
throw validationError(`Queries not tracked on project "${project.name}": ${missing.join(", ")}`, {
|
|
1196
|
+
missing,
|
|
1197
|
+
tracked: [...tracked]
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
scopedQueries = body.queries;
|
|
1201
|
+
}
|
|
1202
|
+
const queriesColumn = scopedQueries ? JSON.stringify(scopedQueries) : null;
|
|
1188
1203
|
let resolvedLocation;
|
|
1189
1204
|
const projectLocations = parseJsonColumn(project.locations, []);
|
|
1190
1205
|
if (body.noLocation) {
|
|
@@ -1217,6 +1232,7 @@ async function runRoutes(app, opts) {
|
|
|
1217
1232
|
status: "queued",
|
|
1218
1233
|
trigger,
|
|
1219
1234
|
location: loc.label,
|
|
1235
|
+
queries: queriesColumn,
|
|
1220
1236
|
createdAt: now
|
|
1221
1237
|
}).run();
|
|
1222
1238
|
newRuns.push({ runId: runId2, loc });
|
|
@@ -1244,7 +1260,8 @@ async function runRoutes(app, opts) {
|
|
|
1244
1260
|
kind,
|
|
1245
1261
|
projectId: project.id,
|
|
1246
1262
|
trigger,
|
|
1247
|
-
location: locationLabel
|
|
1263
|
+
location: locationLabel,
|
|
1264
|
+
queries: queriesColumn
|
|
1248
1265
|
});
|
|
1249
1266
|
if (queueResult.conflict) throw runInProgress(project.name);
|
|
1250
1267
|
const runId = queueResult.runId;
|
|
@@ -1390,6 +1407,7 @@ function formatRun(row) {
|
|
|
1390
1407
|
status: row.status,
|
|
1391
1408
|
trigger: row.trigger,
|
|
1392
1409
|
location: row.location,
|
|
1410
|
+
queries: parseJsonColumn(row.queries, null),
|
|
1393
1411
|
startedAt: row.startedAt,
|
|
1394
1412
|
finishedAt: row.finishedAt,
|
|
1395
1413
|
error: parseRunError(row.error),
|
|
@@ -8003,6 +8021,7 @@ var routeCatalog = [
|
|
|
8003
8021
|
kind: stringSchema,
|
|
8004
8022
|
trigger: stringSchema,
|
|
8005
8023
|
providers: stringArraySchema,
|
|
8024
|
+
queries: stringArraySchema,
|
|
8006
8025
|
location: stringSchema,
|
|
8007
8026
|
allLocations: booleanSchema,
|
|
8008
8027
|
noLocation: booleanSchema
|
|
@@ -10171,8 +10190,8 @@ var routeCatalog = [
|
|
|
10171
10190
|
{
|
|
10172
10191
|
method: "post",
|
|
10173
10192
|
path: "/api/v1/projects/{name}/traffic/sources/{id}/backfill",
|
|
10174
|
-
summary: "Reclassify historical
|
|
10175
|
-
description: 'Async one-shot backfill: pulls the last `days` of
|
|
10193
|
+
summary: "Reclassify historical traffic-source logs",
|
|
10194
|
+
description: 'Async one-shot backfill: pulls the last `days` of events (clamped server-side to the upstream retention ceiling \u2014 30d for Cloud Logging `_Default`; the WordPress plugin honours the same window via `since`/`until` query params), classifies them with the current rules, and replaces the hourly rollup buckets + sample slice in the window inside one transaction. Returns immediately with `{ runId, status: "running" }`; poll `GET /runs/{id}` for completion. lastSyncedAt only advances forward, so a backfill never undoes incremental sync progress that ran ahead of it. Supported source types: `cloud-run`, `wordpress`.',
|
|
10176
10195
|
tags: ["traffic"],
|
|
10177
10196
|
parameters: [
|
|
10178
10197
|
nameParameter,
|
|
@@ -17411,6 +17430,7 @@ async function listWordpressTrafficEvents(options) {
|
|
|
17411
17430
|
let cursor = options.cursor;
|
|
17412
17431
|
let rawEntryCount = 0;
|
|
17413
17432
|
let skippedEntryCount = 0;
|
|
17433
|
+
let hasMore = false;
|
|
17414
17434
|
const events = [];
|
|
17415
17435
|
for (let page = 0; page < maxPages; page += 1) {
|
|
17416
17436
|
const url = new URL(endpoint);
|
|
@@ -17418,6 +17438,12 @@ async function listWordpressTrafficEvents(options) {
|
|
|
17418
17438
|
if (cursor !== void 0 && cursor !== "") {
|
|
17419
17439
|
url.searchParams.set("cursor", cursor);
|
|
17420
17440
|
}
|
|
17441
|
+
if (options.since !== void 0 && options.since !== "") {
|
|
17442
|
+
url.searchParams.set("since", options.since);
|
|
17443
|
+
}
|
|
17444
|
+
if (options.until !== void 0 && options.until !== "") {
|
|
17445
|
+
url.searchParams.set("until", options.until);
|
|
17446
|
+
}
|
|
17421
17447
|
const response = await fetch(url, {
|
|
17422
17448
|
method: "GET",
|
|
17423
17449
|
headers: {
|
|
@@ -17446,6 +17472,7 @@ async function listWordpressTrafficEvents(options) {
|
|
|
17446
17472
|
}
|
|
17447
17473
|
}
|
|
17448
17474
|
cursor = body.next_cursor ?? void 0;
|
|
17475
|
+
hasMore = Boolean(body.has_more) && Boolean(cursor);
|
|
17449
17476
|
if (!body.has_more || !cursor) break;
|
|
17450
17477
|
}
|
|
17451
17478
|
return {
|
|
@@ -17453,6 +17480,7 @@ async function listWordpressTrafficEvents(options) {
|
|
|
17453
17480
|
rawEntryCount,
|
|
17454
17481
|
skippedEntryCount,
|
|
17455
17482
|
nextCursor: cursor,
|
|
17483
|
+
hasMore,
|
|
17456
17484
|
endpoint
|
|
17457
17485
|
};
|
|
17458
17486
|
}
|
|
@@ -17462,6 +17490,8 @@ var DEFAULT_SYNC_WINDOW_MINUTES = 43200;
|
|
|
17462
17490
|
var DEFAULT_PAGE_SIZE3 = 1e3;
|
|
17463
17491
|
var DEFAULT_MAX_PAGES3 = 5;
|
|
17464
17492
|
var DEFAULT_SAMPLE_LIMIT2 = 100;
|
|
17493
|
+
var DEFAULT_WP_PAGE_SIZE = 500;
|
|
17494
|
+
var DEFAULT_WP_MAX_PAGES = 20;
|
|
17465
17495
|
var MAX_TRACKED_EVENT_IDS = 1e3;
|
|
17466
17496
|
var DEFAULT_BACKFILL_DAYS = 30;
|
|
17467
17497
|
var MAX_BACKFILL_DAYS = 30;
|
|
@@ -17503,14 +17533,10 @@ async function runBackfillTask(options) {
|
|
|
17503
17533
|
runId,
|
|
17504
17534
|
project,
|
|
17505
17535
|
sourceRow,
|
|
17506
|
-
gcpProjectId,
|
|
17507
|
-
serviceName,
|
|
17508
|
-
location,
|
|
17509
|
-
credential,
|
|
17510
17536
|
windowStart,
|
|
17511
17537
|
windowEnd,
|
|
17512
|
-
|
|
17513
|
-
|
|
17538
|
+
pullForBackfill,
|
|
17539
|
+
pullErrorPrefix
|
|
17514
17540
|
} = options;
|
|
17515
17541
|
const markFailed = (msg) => {
|
|
17516
17542
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -17522,33 +17548,11 @@ async function runBackfillTask(options) {
|
|
|
17522
17548
|
} catch {
|
|
17523
17549
|
}
|
|
17524
17550
|
};
|
|
17525
|
-
let
|
|
17551
|
+
let allEvents;
|
|
17526
17552
|
try {
|
|
17527
|
-
|
|
17553
|
+
allEvents = await pullForBackfill();
|
|
17528
17554
|
} catch (e) {
|
|
17529
|
-
markFailed(
|
|
17530
|
-
return;
|
|
17531
|
-
}
|
|
17532
|
-
const allEvents = [];
|
|
17533
|
-
try {
|
|
17534
|
-
const page = await pullEvents(accessToken, {
|
|
17535
|
-
gcpProjectId,
|
|
17536
|
-
serviceName,
|
|
17537
|
-
location,
|
|
17538
|
-
startTime: windowStart.toISOString(),
|
|
17539
|
-
endTime: windowEnd.toISOString(),
|
|
17540
|
-
pageSize: DEFAULT_PAGE_SIZE3,
|
|
17541
|
-
maxPages: BACKFILL_MAX_PAGES,
|
|
17542
|
-
// Backfill is intentionally `firstSync: false`. We don't want desc
|
|
17543
|
-
// ordering — the in-memory rollup builder handles any order, and the
|
|
17544
|
-
// ring-buffer reseed at the end takes the most-recent IDs from the
|
|
17545
|
-
// dedupedEvents anyway.
|
|
17546
|
-
firstSync: false,
|
|
17547
|
-
orderBy: "timestamp asc"
|
|
17548
|
-
});
|
|
17549
|
-
allEvents.push(...page.events);
|
|
17550
|
-
} catch (e) {
|
|
17551
|
-
markFailed(`Cloud Run pull failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
17555
|
+
markFailed(`${pullErrorPrefix}: ${e instanceof Error ? e.message : String(e)}`);
|
|
17552
17556
|
return;
|
|
17553
17557
|
}
|
|
17554
17558
|
if (allEvents.length === 0) {
|
|
@@ -17838,33 +17842,12 @@ async function trafficRoutes(app, opts) {
|
|
|
17838
17842
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
17839
17843
|
throw notFound("Traffic source", request.params.id);
|
|
17840
17844
|
}
|
|
17841
|
-
if (sourceRow.sourceType !== TrafficSourceTypes["cloud-run"]) {
|
|
17845
|
+
if (sourceRow.sourceType !== TrafficSourceTypes["cloud-run"] && sourceRow.sourceType !== TrafficSourceTypes.wordpress) {
|
|
17842
17846
|
throw validationError(
|
|
17843
|
-
`Sync for source type "${sourceRow.sourceType}" is not implemented yet \u2014 only cloud-run
|
|
17847
|
+
`Sync for source type "${sourceRow.sourceType}" is not implemented yet \u2014 only cloud-run and wordpress are supported in v1.`
|
|
17844
17848
|
);
|
|
17845
17849
|
}
|
|
17846
|
-
const credentialStore = opts.cloudRunCredentialStore;
|
|
17847
|
-
if (!credentialStore) {
|
|
17848
|
-
throw validationError("Cloud Run credential storage is not configured for this deployment");
|
|
17849
|
-
}
|
|
17850
|
-
const credential = credentialStore.getConnection(project.name);
|
|
17851
|
-
if (!credential) {
|
|
17852
|
-
throw validationError(
|
|
17853
|
-
`No Cloud Run credential found for project "${project.name}". Run "canonry traffic connect cloud-run" first.`
|
|
17854
|
-
);
|
|
17855
|
-
}
|
|
17856
|
-
const config = parseSourceConfig(sourceRow);
|
|
17857
|
-
const gcpProjectId = config.gcpProjectId ?? credential.gcpProjectId;
|
|
17858
|
-
const serviceName = config.serviceName ?? credential.serviceName ?? void 0;
|
|
17859
|
-
const location = config.location ?? credential.location ?? void 0;
|
|
17860
|
-
const requestedMinutes = request.body?.sinceMinutes;
|
|
17861
|
-
const windowMinutes = Number.isFinite(requestedMinutes) && requestedMinutes && requestedMinutes > 0 ? Math.floor(requestedMinutes) : syncWindowMinutes;
|
|
17862
17850
|
const windowEnd = /* @__PURE__ */ new Date();
|
|
17863
|
-
const requestedStartMs = windowEnd.getTime() - windowMinutes * 6e4;
|
|
17864
|
-
const lastSyncedMs = sourceRow.lastSyncedAt ? new Date(sourceRow.lastSyncedAt).getTime() : Number.NEGATIVE_INFINITY;
|
|
17865
|
-
const windowStart = new Date(
|
|
17866
|
-
Math.min(windowEnd.getTime(), Math.max(requestedStartMs, lastSyncedMs))
|
|
17867
|
-
);
|
|
17868
17851
|
const startedAt = windowEnd.toISOString();
|
|
17869
17852
|
const syncStartedAtMs = windowEnd.getTime();
|
|
17870
17853
|
const runId = crypto20.randomUUID();
|
|
@@ -17898,32 +17881,100 @@ async function trafficRoutes(app, opts) {
|
|
|
17898
17881
|
} catch {
|
|
17899
17882
|
}
|
|
17900
17883
|
};
|
|
17901
|
-
let
|
|
17902
|
-
|
|
17903
|
-
|
|
17904
|
-
|
|
17905
|
-
|
|
17906
|
-
|
|
17907
|
-
|
|
17908
|
-
|
|
17909
|
-
|
|
17910
|
-
|
|
17911
|
-
|
|
17912
|
-
|
|
17913
|
-
|
|
17914
|
-
|
|
17915
|
-
|
|
17916
|
-
|
|
17917
|
-
|
|
17918
|
-
|
|
17919
|
-
|
|
17920
|
-
|
|
17921
|
-
|
|
17922
|
-
|
|
17923
|
-
|
|
17924
|
-
const
|
|
17925
|
-
|
|
17926
|
-
|
|
17884
|
+
let windowStart;
|
|
17885
|
+
let allEvents;
|
|
17886
|
+
let nextCursor;
|
|
17887
|
+
let auditAction;
|
|
17888
|
+
if (sourceRow.sourceType === TrafficSourceTypes["cloud-run"]) {
|
|
17889
|
+
auditAction = "traffic.cloud-run.synced";
|
|
17890
|
+
const credentialStore = opts.cloudRunCredentialStore;
|
|
17891
|
+
if (!credentialStore) {
|
|
17892
|
+
throw validationError("Cloud Run credential storage is not configured for this deployment");
|
|
17893
|
+
}
|
|
17894
|
+
const credential = credentialStore.getConnection(project.name);
|
|
17895
|
+
if (!credential) {
|
|
17896
|
+
throw validationError(
|
|
17897
|
+
`No Cloud Run credential found for project "${project.name}". Run "canonry traffic connect cloud-run" first.`
|
|
17898
|
+
);
|
|
17899
|
+
}
|
|
17900
|
+
const config = parseSourceConfig(sourceRow);
|
|
17901
|
+
const gcpProjectId = config.gcpProjectId ?? credential.gcpProjectId;
|
|
17902
|
+
const serviceName = config.serviceName ?? credential.serviceName ?? void 0;
|
|
17903
|
+
const location = config.location ?? credential.location ?? void 0;
|
|
17904
|
+
const requestedMinutes = request.body?.sinceMinutes;
|
|
17905
|
+
const windowMinutes = Number.isFinite(requestedMinutes) && requestedMinutes && requestedMinutes > 0 ? Math.floor(requestedMinutes) : syncWindowMinutes;
|
|
17906
|
+
const requestedStartMs = windowEnd.getTime() - windowMinutes * 6e4;
|
|
17907
|
+
const lastSyncedMs = sourceRow.lastSyncedAt ? new Date(sourceRow.lastSyncedAt).getTime() : Number.NEGATIVE_INFINITY;
|
|
17908
|
+
windowStart = new Date(
|
|
17909
|
+
Math.min(windowEnd.getTime(), Math.max(requestedStartMs, lastSyncedMs))
|
|
17910
|
+
);
|
|
17911
|
+
let accessToken;
|
|
17912
|
+
try {
|
|
17913
|
+
accessToken = await resolveAccessToken2(credential);
|
|
17914
|
+
} catch (e) {
|
|
17915
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
17916
|
+
markFailed(msg, "PROVIDER_AUTH");
|
|
17917
|
+
throw providerError(`Failed to resolve Cloud Run access token: ${msg}`);
|
|
17918
|
+
}
|
|
17919
|
+
const isFirstSync = !sourceRow.lastSyncedAt;
|
|
17920
|
+
try {
|
|
17921
|
+
const page = await pullEvents(accessToken, {
|
|
17922
|
+
gcpProjectId,
|
|
17923
|
+
serviceName,
|
|
17924
|
+
location,
|
|
17925
|
+
startTime: windowStart.toISOString(),
|
|
17926
|
+
endTime: windowEnd.toISOString(),
|
|
17927
|
+
pageSize,
|
|
17928
|
+
maxPages,
|
|
17929
|
+
firstSync: isFirstSync
|
|
17930
|
+
});
|
|
17931
|
+
allEvents = page.events;
|
|
17932
|
+
} catch (e) {
|
|
17933
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
17934
|
+
markFailed(msg, "PROVIDER_PULL");
|
|
17935
|
+
throw providerError(`Cloud Run pull failed: ${msg}`);
|
|
17936
|
+
}
|
|
17937
|
+
} else {
|
|
17938
|
+
auditAction = "traffic.wordpress.synced";
|
|
17939
|
+
const credentialStore = opts.wordpressTrafficCredentialStore;
|
|
17940
|
+
if (!credentialStore) {
|
|
17941
|
+
throw validationError("WordPress traffic credential storage is not configured for this deployment");
|
|
17942
|
+
}
|
|
17943
|
+
const credential = credentialStore.getConnection(project.name);
|
|
17944
|
+
if (!credential) {
|
|
17945
|
+
app.db.delete(runs).where(eq23(runs.id, runId)).run();
|
|
17946
|
+
throw validationError(
|
|
17947
|
+
`No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
|
|
17948
|
+
);
|
|
17949
|
+
}
|
|
17950
|
+
windowStart = sourceRow.lastSyncedAt ? new Date(sourceRow.lastSyncedAt) : windowEnd;
|
|
17951
|
+
const wpPageSize = opts.defaultWordpressPageSize ?? DEFAULT_WP_PAGE_SIZE;
|
|
17952
|
+
const wpMaxPages = opts.defaultWordpressMaxPages ?? DEFAULT_WP_MAX_PAGES;
|
|
17953
|
+
const collected = [];
|
|
17954
|
+
let cursor = sourceRow.lastCursor ?? void 0;
|
|
17955
|
+
try {
|
|
17956
|
+
for (let page = 0; page < wpMaxPages; page += 1) {
|
|
17957
|
+
const pageResult = await pullWordpressEvents({
|
|
17958
|
+
baseUrl: credential.baseUrl,
|
|
17959
|
+
username: credential.username,
|
|
17960
|
+
applicationPassword: credential.applicationPassword,
|
|
17961
|
+
cursor,
|
|
17962
|
+
pageSize: wpPageSize,
|
|
17963
|
+
maxPages: 1
|
|
17964
|
+
});
|
|
17965
|
+
collected.push(...pageResult.events);
|
|
17966
|
+
const previousCursor = cursor;
|
|
17967
|
+
cursor = pageResult.nextCursor;
|
|
17968
|
+
if (!pageResult.hasMore) break;
|
|
17969
|
+
if (!cursor || cursor === previousCursor) break;
|
|
17970
|
+
}
|
|
17971
|
+
allEvents = collected;
|
|
17972
|
+
nextCursor = cursor;
|
|
17973
|
+
} catch (e) {
|
|
17974
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
17975
|
+
markFailed(msg, "PROVIDER_PULL");
|
|
17976
|
+
throw providerError(`WordPress pull failed: ${msg}`);
|
|
17977
|
+
}
|
|
17927
17978
|
}
|
|
17928
17979
|
let crawlerBucketRows = 0;
|
|
17929
17980
|
let aiReferralBucketRows = 0;
|
|
@@ -18050,7 +18101,7 @@ async function trafficRoutes(app, opts) {
|
|
|
18050
18101
|
}).run();
|
|
18051
18102
|
sampleRows += 1;
|
|
18052
18103
|
}
|
|
18053
|
-
|
|
18104
|
+
const sourceUpdate = {
|
|
18054
18105
|
status: TrafficSourceStatuses.connected,
|
|
18055
18106
|
// Advance to windowEnd, not finishedAt — events arriving at the
|
|
18056
18107
|
// source between windowEnd and finishedAt aren't in this pull's
|
|
@@ -18060,13 +18111,17 @@ async function trafficRoutes(app, opts) {
|
|
|
18060
18111
|
lastError: null,
|
|
18061
18112
|
lastEventIds: JSON.stringify(nextEventIds),
|
|
18062
18113
|
updatedAt: finishedAt
|
|
18063
|
-
}
|
|
18114
|
+
};
|
|
18115
|
+
if (sourceRow.sourceType === TrafficSourceTypes.wordpress) {
|
|
18116
|
+
sourceUpdate.lastCursor = nextCursor ?? null;
|
|
18117
|
+
}
|
|
18118
|
+
tx.update(trafficSources).set(sourceUpdate).where(eq23(trafficSources.id, sourceRow.id)).run();
|
|
18064
18119
|
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq23(runs.id, runId)).run();
|
|
18065
18120
|
});
|
|
18066
18121
|
writeAuditLog(app.db, {
|
|
18067
18122
|
projectId: project.id,
|
|
18068
18123
|
actor: "api",
|
|
18069
|
-
action:
|
|
18124
|
+
action: auditAction,
|
|
18070
18125
|
entityType: "traffic_source",
|
|
18071
18126
|
entityId: sourceRow.id
|
|
18072
18127
|
});
|
|
@@ -18104,19 +18159,9 @@ async function trafficRoutes(app, opts) {
|
|
|
18104
18159
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
18105
18160
|
throw notFound("Traffic source", request.params.id);
|
|
18106
18161
|
}
|
|
18107
|
-
if (sourceRow.sourceType !== TrafficSourceTypes["cloud-run"]) {
|
|
18162
|
+
if (sourceRow.sourceType !== TrafficSourceTypes["cloud-run"] && sourceRow.sourceType !== TrafficSourceTypes.wordpress) {
|
|
18108
18163
|
throw validationError(
|
|
18109
|
-
`Backfill for source type "${sourceRow.sourceType}" is not implemented yet \u2014 only cloud-run
|
|
18110
|
-
);
|
|
18111
|
-
}
|
|
18112
|
-
const credentialStore = opts.cloudRunCredentialStore;
|
|
18113
|
-
if (!credentialStore) {
|
|
18114
|
-
throw validationError("Cloud Run credential storage is not configured for this deployment");
|
|
18115
|
-
}
|
|
18116
|
-
const credential = credentialStore.getConnection(project.name);
|
|
18117
|
-
if (!credential) {
|
|
18118
|
-
throw validationError(
|
|
18119
|
-
`No Cloud Run credential found for project "${project.name}". Run "canonry traffic connect cloud-run" first.`
|
|
18164
|
+
`Backfill for source type "${sourceRow.sourceType}" is not implemented yet \u2014 only cloud-run and wordpress are supported in v1.`
|
|
18120
18165
|
);
|
|
18121
18166
|
}
|
|
18122
18167
|
const requestedDays = request.body?.days ?? DEFAULT_BACKFILL_DAYS;
|
|
@@ -18124,13 +18169,86 @@ async function trafficRoutes(app, opts) {
|
|
|
18124
18169
|
throw validationError('"days" must be a positive integer');
|
|
18125
18170
|
}
|
|
18126
18171
|
const appliedDays = Math.min(requestedDays, MAX_BACKFILL_DAYS);
|
|
18127
|
-
const config = parseSourceConfig(sourceRow);
|
|
18128
|
-
const gcpProjectId = config.gcpProjectId ?? credential.gcpProjectId;
|
|
18129
|
-
const serviceName = config.serviceName ?? credential.serviceName ?? void 0;
|
|
18130
|
-
const location = config.location ?? credential.location ?? void 0;
|
|
18131
18172
|
const windowEnd = /* @__PURE__ */ new Date();
|
|
18132
18173
|
const windowStart = new Date(windowEnd.getTime() - appliedDays * 864e5);
|
|
18133
18174
|
windowStart.setUTCMinutes(0, 0, 0);
|
|
18175
|
+
let pullForBackfill;
|
|
18176
|
+
let pullErrorPrefix;
|
|
18177
|
+
if (sourceRow.sourceType === TrafficSourceTypes["cloud-run"]) {
|
|
18178
|
+
const credentialStore = opts.cloudRunCredentialStore;
|
|
18179
|
+
if (!credentialStore) {
|
|
18180
|
+
throw validationError("Cloud Run credential storage is not configured for this deployment");
|
|
18181
|
+
}
|
|
18182
|
+
const credential = credentialStore.getConnection(project.name);
|
|
18183
|
+
if (!credential) {
|
|
18184
|
+
throw validationError(
|
|
18185
|
+
`No Cloud Run credential found for project "${project.name}". Run "canonry traffic connect cloud-run" first.`
|
|
18186
|
+
);
|
|
18187
|
+
}
|
|
18188
|
+
const config = parseSourceConfig(sourceRow);
|
|
18189
|
+
const gcpProjectId = config.gcpProjectId ?? credential.gcpProjectId;
|
|
18190
|
+
const serviceName = config.serviceName ?? credential.serviceName ?? void 0;
|
|
18191
|
+
const location = config.location ?? credential.location ?? void 0;
|
|
18192
|
+
pullErrorPrefix = "Cloud Run pull failed";
|
|
18193
|
+
pullForBackfill = async () => {
|
|
18194
|
+
const accessToken = await resolveAccessToken2(credential);
|
|
18195
|
+
const page = await pullEvents(accessToken, {
|
|
18196
|
+
gcpProjectId,
|
|
18197
|
+
serviceName,
|
|
18198
|
+
location,
|
|
18199
|
+
startTime: windowStart.toISOString(),
|
|
18200
|
+
endTime: windowEnd.toISOString(),
|
|
18201
|
+
pageSize: DEFAULT_PAGE_SIZE3,
|
|
18202
|
+
maxPages: BACKFILL_MAX_PAGES,
|
|
18203
|
+
// Backfill is intentionally `firstSync: false`. We don't want desc
|
|
18204
|
+
// ordering — the in-memory rollup builder handles any order, and the
|
|
18205
|
+
// ring-buffer reseed at the end takes the most-recent IDs from the
|
|
18206
|
+
// dedupedEvents anyway.
|
|
18207
|
+
firstSync: false,
|
|
18208
|
+
orderBy: "timestamp asc"
|
|
18209
|
+
});
|
|
18210
|
+
return page.events;
|
|
18211
|
+
};
|
|
18212
|
+
} else {
|
|
18213
|
+
const credentialStore = opts.wordpressTrafficCredentialStore;
|
|
18214
|
+
if (!credentialStore) {
|
|
18215
|
+
throw validationError("WordPress traffic credential storage is not configured for this deployment");
|
|
18216
|
+
}
|
|
18217
|
+
const credential = credentialStore.getConnection(project.name);
|
|
18218
|
+
if (!credential) {
|
|
18219
|
+
throw validationError(
|
|
18220
|
+
`No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
|
|
18221
|
+
);
|
|
18222
|
+
}
|
|
18223
|
+
const wpPageSize = opts.defaultWordpressPageSize ?? DEFAULT_WP_PAGE_SIZE;
|
|
18224
|
+
pullErrorPrefix = "WordPress pull failed";
|
|
18225
|
+
pullForBackfill = async () => {
|
|
18226
|
+
const collected = [];
|
|
18227
|
+
const windowStartIso = windowStart.toISOString();
|
|
18228
|
+
const windowEndIso = windowEnd.toISOString();
|
|
18229
|
+
let cursor = void 0;
|
|
18230
|
+
for (let page = 0; page < BACKFILL_MAX_PAGES; page += 1) {
|
|
18231
|
+
const pageResult = await pullWordpressEvents({
|
|
18232
|
+
baseUrl: credential.baseUrl,
|
|
18233
|
+
username: credential.username,
|
|
18234
|
+
applicationPassword: credential.applicationPassword,
|
|
18235
|
+
cursor,
|
|
18236
|
+
pageSize: wpPageSize,
|
|
18237
|
+
// Each call fetches a single page; the for-loop drives
|
|
18238
|
+
// continuation. Matches the WP sync path's pattern.
|
|
18239
|
+
maxPages: 1,
|
|
18240
|
+
since: windowStartIso,
|
|
18241
|
+
until: windowEndIso
|
|
18242
|
+
});
|
|
18243
|
+
collected.push(...pageResult.events);
|
|
18244
|
+
const previousCursor = cursor;
|
|
18245
|
+
cursor = pageResult.nextCursor;
|
|
18246
|
+
if (!pageResult.hasMore) break;
|
|
18247
|
+
if (!cursor || cursor === previousCursor) break;
|
|
18248
|
+
}
|
|
18249
|
+
return collected;
|
|
18250
|
+
};
|
|
18251
|
+
}
|
|
18134
18252
|
const startedAt = windowEnd.toISOString();
|
|
18135
18253
|
const runId = crypto20.randomUUID();
|
|
18136
18254
|
app.db.insert(runs).values({
|
|
@@ -18148,15 +18266,10 @@ async function trafficRoutes(app, opts) {
|
|
|
18148
18266
|
runId,
|
|
18149
18267
|
project,
|
|
18150
18268
|
sourceRow,
|
|
18151
|
-
gcpProjectId,
|
|
18152
|
-
serviceName,
|
|
18153
|
-
location,
|
|
18154
|
-
credential,
|
|
18155
18269
|
windowStart,
|
|
18156
18270
|
windowEnd,
|
|
18157
|
-
|
|
18158
|
-
|
|
18159
|
-
resolveAccessToken: resolveAccessToken2
|
|
18271
|
+
pullForBackfill,
|
|
18272
|
+
pullErrorPrefix
|
|
18160
18273
|
}).catch(() => {
|
|
18161
18274
|
});
|
|
18162
18275
|
const response = {
|
|
@@ -22887,7 +23000,8 @@ var JobRunner = class {
|
|
|
22887
23000
|
throw new Error("No providers configured. Add at least one provider API key.");
|
|
22888
23001
|
}
|
|
22889
23002
|
log.info("run.dispatch", { runId, providerCount: activeProviders.length, providers: activeProviders.map((p) => p.adapter.name) });
|
|
22890
|
-
|
|
23003
|
+
const scopedQueryNames = parseJsonColumn(existingRun.queries, null);
|
|
23004
|
+
projectQueries = scopedQueryNames ? this.db.select().from(queries).where(and16(eq27(queries.projectId, projectId), inArray7(queries.query, scopedQueryNames))).all() : this.db.select().from(queries).where(eq27(queries.projectId, projectId)).all();
|
|
22891
23005
|
const projectCompetitors = this.db.select().from(competitors).where(eq27(competitors.projectId, projectId)).all();
|
|
22892
23006
|
const competitorDomains = projectCompetitors.map((c) => c.domain);
|
|
22893
23007
|
const allDomains = effectiveDomains({
|
|
@@ -23167,7 +23281,8 @@ var JobRunner = class {
|
|
|
23167
23281
|
status: runs.status,
|
|
23168
23282
|
finishedAt: runs.finishedAt,
|
|
23169
23283
|
error: runs.error,
|
|
23170
|
-
trigger: runs.trigger
|
|
23284
|
+
trigger: runs.trigger,
|
|
23285
|
+
queries: runs.queries
|
|
23171
23286
|
}).from(runs).where(eq27(runs.id, runId)).get();
|
|
23172
23287
|
}
|
|
23173
23288
|
isRunCancelled(runId) {
|
|
@@ -105,6 +105,7 @@ var runTriggerRequestSchema = z2.object({
|
|
|
105
105
|
kind: z2.literal(RunKinds["answer-visibility"]).optional(),
|
|
106
106
|
trigger: z2.literal(RunTriggers.manual).optional(),
|
|
107
107
|
providers: z2.array(providerNameSchema).optional(),
|
|
108
|
+
queries: z2.array(z2.string().min(1)).min(1).optional(),
|
|
108
109
|
location: z2.string().min(1).optional(),
|
|
109
110
|
allLocations: z2.boolean().optional(),
|
|
110
111
|
noLocation: z2.boolean().optional()
|
|
@@ -131,6 +132,7 @@ var runDtoSchema = z2.object({
|
|
|
131
132
|
status: runStatusSchema,
|
|
132
133
|
trigger: runTriggerSchema.default("manual"),
|
|
133
134
|
location: z2.string().nullable().optional(),
|
|
135
|
+
queries: z2.array(z2.string()).nullable().optional(),
|
|
134
136
|
startedAt: z2.string().nullable().optional(),
|
|
135
137
|
finishedAt: z2.string().nullable().optional(),
|
|
136
138
|
error: runErrorSchema.nullable().optional(),
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
categoryLabel,
|
|
9
9
|
determineAnswerMentioned,
|
|
10
10
|
normalizeProjectDomain
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-HVW665A4.js";
|
|
12
12
|
|
|
13
13
|
// src/intelligence-service.ts
|
|
14
14
|
import { eq, desc, asc, and, or, inArray } from "drizzle-orm";
|
|
@@ -109,6 +109,7 @@ var runs = sqliteTable("runs", {
|
|
|
109
109
|
status: text("status").notNull().default("queued"),
|
|
110
110
|
trigger: text("trigger").notNull().default("manual"),
|
|
111
111
|
location: text("location"),
|
|
112
|
+
queries: text("queries"),
|
|
112
113
|
sourceId: text("source_id"),
|
|
113
114
|
startedAt: text("started_at"),
|
|
114
115
|
finishedAt: text("finished_at"),
|
|
@@ -1792,6 +1793,17 @@ var MIGRATION_VERSIONS = [
|
|
|
1792
1793
|
`ALTER TABLE discovery_sessions ADD COLUMN run_id TEXT`,
|
|
1793
1794
|
`CREATE INDEX IF NOT EXISTS idx_discovery_sessions_run ON discovery_sessions(run_id)`
|
|
1794
1795
|
]
|
|
1796
|
+
},
|
|
1797
|
+
{
|
|
1798
|
+
version: 57,
|
|
1799
|
+
name: "runs-scoped-queries",
|
|
1800
|
+
// Persists an optional subset of tracked queries to sweep on a per-run
|
|
1801
|
+
// basis. NULL = full sweep (the default and only behavior pre-v57); a JSON
|
|
1802
|
+
// array of query strings = scope. The job runner reads this to filter the
|
|
1803
|
+
// query fetch via `inArray`.
|
|
1804
|
+
statements: [
|
|
1805
|
+
`ALTER TABLE runs ADD COLUMN queries TEXT`
|
|
1806
|
+
]
|
|
1795
1807
|
}
|
|
1796
1808
|
];
|
|
1797
1809
|
function isDuplicateColumnError(err) {
|