@ainyc/canonry 4.25.0 → 4.26.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.
- 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-BrlkSJ58.js} +94 -94
- package/assets/index.html +1 -1
- package/dist/{chunk-A7HQ6X43.js → chunk-2FAEQ56I.js} +2 -2
- package/dist/{chunk-CRQMGNPH.js → chunk-HVW665A4.js} +2 -0
- package/dist/{chunk-6J6WQOGH.js → chunk-M3HZAUWZ.js} +232 -116
- package/dist/{chunk-IS65IYNZ.js → chunk-PN24DAGC.js} +13 -1
- package/dist/cli.js +24 -9
- 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 +9 -9
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-BrlkSJ58.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,
|
|
@@ -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(),
|
|
@@ -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;
|
|
@@ -1272,7 +1289,7 @@ async function runRoutes(app, opts) {
|
|
|
1272
1289
|
const project = resolveProject(app.db, request.params.name);
|
|
1273
1290
|
const countRow = app.db.select({ count: sql2`count(*)` }).from(runs).where(eq7(runs.projectId, project.id)).get();
|
|
1274
1291
|
const totalRuns = countRow?.count ?? 0;
|
|
1275
|
-
const latestRun = app.db.select().from(runs).where(eq7(runs.projectId, project.id)).orderBy(desc(runs.createdAt)).limit(1).get();
|
|
1292
|
+
const latestRun = app.db.select().from(runs).where(eq7(runs.projectId, project.id)).orderBy(desc(runs.createdAt), desc(runs.id)).limit(1).get();
|
|
1276
1293
|
if (!latestRun) {
|
|
1277
1294
|
return reply.send({ totalRuns: 0, run: null });
|
|
1278
1295
|
}
|
|
@@ -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),
|
|
@@ -2136,7 +2154,8 @@ async function historyRoutes(app) {
|
|
|
2136
2154
|
transition,
|
|
2137
2155
|
answerMentioned: snap.answerMentioned,
|
|
2138
2156
|
visibilityState: snap.answerMentioned ? "visible" : "not-visible",
|
|
2139
|
-
visibilityTransition
|
|
2157
|
+
visibilityTransition,
|
|
2158
|
+
location: snap.location
|
|
2140
2159
|
};
|
|
2141
2160
|
});
|
|
2142
2161
|
}
|
|
@@ -8003,6 +8022,7 @@ var routeCatalog = [
|
|
|
8003
8022
|
kind: stringSchema,
|
|
8004
8023
|
trigger: stringSchema,
|
|
8005
8024
|
providers: stringArraySchema,
|
|
8025
|
+
queries: stringArraySchema,
|
|
8006
8026
|
location: stringSchema,
|
|
8007
8027
|
allLocations: booleanSchema,
|
|
8008
8028
|
noLocation: booleanSchema
|
|
@@ -10171,8 +10191,8 @@ var routeCatalog = [
|
|
|
10171
10191
|
{
|
|
10172
10192
|
method: "post",
|
|
10173
10193
|
path: "/api/v1/projects/{name}/traffic/sources/{id}/backfill",
|
|
10174
|
-
summary: "Reclassify historical
|
|
10175
|
-
description: 'Async one-shot backfill: pulls the last `days` of
|
|
10194
|
+
summary: "Reclassify historical traffic-source logs",
|
|
10195
|
+
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
10196
|
tags: ["traffic"],
|
|
10177
10197
|
parameters: [
|
|
10178
10198
|
nameParameter,
|
|
@@ -17411,6 +17431,7 @@ async function listWordpressTrafficEvents(options) {
|
|
|
17411
17431
|
let cursor = options.cursor;
|
|
17412
17432
|
let rawEntryCount = 0;
|
|
17413
17433
|
let skippedEntryCount = 0;
|
|
17434
|
+
let hasMore = false;
|
|
17414
17435
|
const events = [];
|
|
17415
17436
|
for (let page = 0; page < maxPages; page += 1) {
|
|
17416
17437
|
const url = new URL(endpoint);
|
|
@@ -17418,6 +17439,12 @@ async function listWordpressTrafficEvents(options) {
|
|
|
17418
17439
|
if (cursor !== void 0 && cursor !== "") {
|
|
17419
17440
|
url.searchParams.set("cursor", cursor);
|
|
17420
17441
|
}
|
|
17442
|
+
if (options.since !== void 0 && options.since !== "") {
|
|
17443
|
+
url.searchParams.set("since", options.since);
|
|
17444
|
+
}
|
|
17445
|
+
if (options.until !== void 0 && options.until !== "") {
|
|
17446
|
+
url.searchParams.set("until", options.until);
|
|
17447
|
+
}
|
|
17421
17448
|
const response = await fetch(url, {
|
|
17422
17449
|
method: "GET",
|
|
17423
17450
|
headers: {
|
|
@@ -17446,6 +17473,7 @@ async function listWordpressTrafficEvents(options) {
|
|
|
17446
17473
|
}
|
|
17447
17474
|
}
|
|
17448
17475
|
cursor = body.next_cursor ?? void 0;
|
|
17476
|
+
hasMore = Boolean(body.has_more) && Boolean(cursor);
|
|
17449
17477
|
if (!body.has_more || !cursor) break;
|
|
17450
17478
|
}
|
|
17451
17479
|
return {
|
|
@@ -17453,6 +17481,7 @@ async function listWordpressTrafficEvents(options) {
|
|
|
17453
17481
|
rawEntryCount,
|
|
17454
17482
|
skippedEntryCount,
|
|
17455
17483
|
nextCursor: cursor,
|
|
17484
|
+
hasMore,
|
|
17456
17485
|
endpoint
|
|
17457
17486
|
};
|
|
17458
17487
|
}
|
|
@@ -17462,6 +17491,8 @@ var DEFAULT_SYNC_WINDOW_MINUTES = 43200;
|
|
|
17462
17491
|
var DEFAULT_PAGE_SIZE3 = 1e3;
|
|
17463
17492
|
var DEFAULT_MAX_PAGES3 = 5;
|
|
17464
17493
|
var DEFAULT_SAMPLE_LIMIT2 = 100;
|
|
17494
|
+
var DEFAULT_WP_PAGE_SIZE = 500;
|
|
17495
|
+
var DEFAULT_WP_MAX_PAGES = 20;
|
|
17465
17496
|
var MAX_TRACKED_EVENT_IDS = 1e3;
|
|
17466
17497
|
var DEFAULT_BACKFILL_DAYS = 30;
|
|
17467
17498
|
var MAX_BACKFILL_DAYS = 30;
|
|
@@ -17503,14 +17534,10 @@ async function runBackfillTask(options) {
|
|
|
17503
17534
|
runId,
|
|
17504
17535
|
project,
|
|
17505
17536
|
sourceRow,
|
|
17506
|
-
gcpProjectId,
|
|
17507
|
-
serviceName,
|
|
17508
|
-
location,
|
|
17509
|
-
credential,
|
|
17510
17537
|
windowStart,
|
|
17511
17538
|
windowEnd,
|
|
17512
|
-
|
|
17513
|
-
|
|
17539
|
+
pullForBackfill,
|
|
17540
|
+
pullErrorPrefix
|
|
17514
17541
|
} = options;
|
|
17515
17542
|
const markFailed = (msg) => {
|
|
17516
17543
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -17522,33 +17549,11 @@ async function runBackfillTask(options) {
|
|
|
17522
17549
|
} catch {
|
|
17523
17550
|
}
|
|
17524
17551
|
};
|
|
17525
|
-
let
|
|
17552
|
+
let allEvents;
|
|
17526
17553
|
try {
|
|
17527
|
-
|
|
17554
|
+
allEvents = await pullForBackfill();
|
|
17528
17555
|
} 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)}`);
|
|
17556
|
+
markFailed(`${pullErrorPrefix}: ${e instanceof Error ? e.message : String(e)}`);
|
|
17552
17557
|
return;
|
|
17553
17558
|
}
|
|
17554
17559
|
if (allEvents.length === 0) {
|
|
@@ -17838,33 +17843,12 @@ async function trafficRoutes(app, opts) {
|
|
|
17838
17843
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
17839
17844
|
throw notFound("Traffic source", request.params.id);
|
|
17840
17845
|
}
|
|
17841
|
-
if (sourceRow.sourceType !== TrafficSourceTypes["cloud-run"]) {
|
|
17846
|
+
if (sourceRow.sourceType !== TrafficSourceTypes["cloud-run"] && sourceRow.sourceType !== TrafficSourceTypes.wordpress) {
|
|
17842
17847
|
throw validationError(
|
|
17843
|
-
`Sync for source type "${sourceRow.sourceType}" is not implemented yet \u2014 only cloud-run
|
|
17848
|
+
`Sync for source type "${sourceRow.sourceType}" is not implemented yet \u2014 only cloud-run and wordpress are supported in v1.`
|
|
17844
17849
|
);
|
|
17845
17850
|
}
|
|
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
17851
|
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
17852
|
const startedAt = windowEnd.toISOString();
|
|
17869
17853
|
const syncStartedAtMs = windowEnd.getTime();
|
|
17870
17854
|
const runId = crypto20.randomUUID();
|
|
@@ -17898,32 +17882,100 @@ async function trafficRoutes(app, opts) {
|
|
|
17898
17882
|
} catch {
|
|
17899
17883
|
}
|
|
17900
17884
|
};
|
|
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
|
-
|
|
17885
|
+
let windowStart;
|
|
17886
|
+
let allEvents;
|
|
17887
|
+
let nextCursor;
|
|
17888
|
+
let auditAction;
|
|
17889
|
+
if (sourceRow.sourceType === TrafficSourceTypes["cloud-run"]) {
|
|
17890
|
+
auditAction = "traffic.cloud-run.synced";
|
|
17891
|
+
const credentialStore = opts.cloudRunCredentialStore;
|
|
17892
|
+
if (!credentialStore) {
|
|
17893
|
+
throw validationError("Cloud Run credential storage is not configured for this deployment");
|
|
17894
|
+
}
|
|
17895
|
+
const credential = credentialStore.getConnection(project.name);
|
|
17896
|
+
if (!credential) {
|
|
17897
|
+
throw validationError(
|
|
17898
|
+
`No Cloud Run credential found for project "${project.name}". Run "canonry traffic connect cloud-run" first.`
|
|
17899
|
+
);
|
|
17900
|
+
}
|
|
17901
|
+
const config = parseSourceConfig(sourceRow);
|
|
17902
|
+
const gcpProjectId = config.gcpProjectId ?? credential.gcpProjectId;
|
|
17903
|
+
const serviceName = config.serviceName ?? credential.serviceName ?? void 0;
|
|
17904
|
+
const location = config.location ?? credential.location ?? void 0;
|
|
17905
|
+
const requestedMinutes = request.body?.sinceMinutes;
|
|
17906
|
+
const windowMinutes = Number.isFinite(requestedMinutes) && requestedMinutes && requestedMinutes > 0 ? Math.floor(requestedMinutes) : syncWindowMinutes;
|
|
17907
|
+
const requestedStartMs = windowEnd.getTime() - windowMinutes * 6e4;
|
|
17908
|
+
const lastSyncedMs = sourceRow.lastSyncedAt ? new Date(sourceRow.lastSyncedAt).getTime() : Number.NEGATIVE_INFINITY;
|
|
17909
|
+
windowStart = new Date(
|
|
17910
|
+
Math.min(windowEnd.getTime(), Math.max(requestedStartMs, lastSyncedMs))
|
|
17911
|
+
);
|
|
17912
|
+
let accessToken;
|
|
17913
|
+
try {
|
|
17914
|
+
accessToken = await resolveAccessToken2(credential);
|
|
17915
|
+
} catch (e) {
|
|
17916
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
17917
|
+
markFailed(msg, "PROVIDER_AUTH");
|
|
17918
|
+
throw providerError(`Failed to resolve Cloud Run access token: ${msg}`);
|
|
17919
|
+
}
|
|
17920
|
+
const isFirstSync = !sourceRow.lastSyncedAt;
|
|
17921
|
+
try {
|
|
17922
|
+
const page = await pullEvents(accessToken, {
|
|
17923
|
+
gcpProjectId,
|
|
17924
|
+
serviceName,
|
|
17925
|
+
location,
|
|
17926
|
+
startTime: windowStart.toISOString(),
|
|
17927
|
+
endTime: windowEnd.toISOString(),
|
|
17928
|
+
pageSize,
|
|
17929
|
+
maxPages,
|
|
17930
|
+
firstSync: isFirstSync
|
|
17931
|
+
});
|
|
17932
|
+
allEvents = page.events;
|
|
17933
|
+
} catch (e) {
|
|
17934
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
17935
|
+
markFailed(msg, "PROVIDER_PULL");
|
|
17936
|
+
throw providerError(`Cloud Run pull failed: ${msg}`);
|
|
17937
|
+
}
|
|
17938
|
+
} else {
|
|
17939
|
+
auditAction = "traffic.wordpress.synced";
|
|
17940
|
+
const credentialStore = opts.wordpressTrafficCredentialStore;
|
|
17941
|
+
if (!credentialStore) {
|
|
17942
|
+
throw validationError("WordPress traffic credential storage is not configured for this deployment");
|
|
17943
|
+
}
|
|
17944
|
+
const credential = credentialStore.getConnection(project.name);
|
|
17945
|
+
if (!credential) {
|
|
17946
|
+
app.db.delete(runs).where(eq23(runs.id, runId)).run();
|
|
17947
|
+
throw validationError(
|
|
17948
|
+
`No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
|
|
17949
|
+
);
|
|
17950
|
+
}
|
|
17951
|
+
windowStart = sourceRow.lastSyncedAt ? new Date(sourceRow.lastSyncedAt) : windowEnd;
|
|
17952
|
+
const wpPageSize = opts.defaultWordpressPageSize ?? DEFAULT_WP_PAGE_SIZE;
|
|
17953
|
+
const wpMaxPages = opts.defaultWordpressMaxPages ?? DEFAULT_WP_MAX_PAGES;
|
|
17954
|
+
const collected = [];
|
|
17955
|
+
let cursor = sourceRow.lastCursor ?? void 0;
|
|
17956
|
+
try {
|
|
17957
|
+
for (let page = 0; page < wpMaxPages; page += 1) {
|
|
17958
|
+
const pageResult = await pullWordpressEvents({
|
|
17959
|
+
baseUrl: credential.baseUrl,
|
|
17960
|
+
username: credential.username,
|
|
17961
|
+
applicationPassword: credential.applicationPassword,
|
|
17962
|
+
cursor,
|
|
17963
|
+
pageSize: wpPageSize,
|
|
17964
|
+
maxPages: 1
|
|
17965
|
+
});
|
|
17966
|
+
collected.push(...pageResult.events);
|
|
17967
|
+
const previousCursor = cursor;
|
|
17968
|
+
cursor = pageResult.nextCursor;
|
|
17969
|
+
if (!pageResult.hasMore) break;
|
|
17970
|
+
if (!cursor || cursor === previousCursor) break;
|
|
17971
|
+
}
|
|
17972
|
+
allEvents = collected;
|
|
17973
|
+
nextCursor = cursor;
|
|
17974
|
+
} catch (e) {
|
|
17975
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
17976
|
+
markFailed(msg, "PROVIDER_PULL");
|
|
17977
|
+
throw providerError(`WordPress pull failed: ${msg}`);
|
|
17978
|
+
}
|
|
17927
17979
|
}
|
|
17928
17980
|
let crawlerBucketRows = 0;
|
|
17929
17981
|
let aiReferralBucketRows = 0;
|
|
@@ -18050,7 +18102,7 @@ async function trafficRoutes(app, opts) {
|
|
|
18050
18102
|
}).run();
|
|
18051
18103
|
sampleRows += 1;
|
|
18052
18104
|
}
|
|
18053
|
-
|
|
18105
|
+
const sourceUpdate = {
|
|
18054
18106
|
status: TrafficSourceStatuses.connected,
|
|
18055
18107
|
// Advance to windowEnd, not finishedAt — events arriving at the
|
|
18056
18108
|
// source between windowEnd and finishedAt aren't in this pull's
|
|
@@ -18060,13 +18112,17 @@ async function trafficRoutes(app, opts) {
|
|
|
18060
18112
|
lastError: null,
|
|
18061
18113
|
lastEventIds: JSON.stringify(nextEventIds),
|
|
18062
18114
|
updatedAt: finishedAt
|
|
18063
|
-
}
|
|
18115
|
+
};
|
|
18116
|
+
if (sourceRow.sourceType === TrafficSourceTypes.wordpress) {
|
|
18117
|
+
sourceUpdate.lastCursor = nextCursor ?? null;
|
|
18118
|
+
}
|
|
18119
|
+
tx.update(trafficSources).set(sourceUpdate).where(eq23(trafficSources.id, sourceRow.id)).run();
|
|
18064
18120
|
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq23(runs.id, runId)).run();
|
|
18065
18121
|
});
|
|
18066
18122
|
writeAuditLog(app.db, {
|
|
18067
18123
|
projectId: project.id,
|
|
18068
18124
|
actor: "api",
|
|
18069
|
-
action:
|
|
18125
|
+
action: auditAction,
|
|
18070
18126
|
entityType: "traffic_source",
|
|
18071
18127
|
entityId: sourceRow.id
|
|
18072
18128
|
});
|
|
@@ -18104,19 +18160,9 @@ async function trafficRoutes(app, opts) {
|
|
|
18104
18160
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
18105
18161
|
throw notFound("Traffic source", request.params.id);
|
|
18106
18162
|
}
|
|
18107
|
-
if (sourceRow.sourceType !== TrafficSourceTypes["cloud-run"]) {
|
|
18163
|
+
if (sourceRow.sourceType !== TrafficSourceTypes["cloud-run"] && sourceRow.sourceType !== TrafficSourceTypes.wordpress) {
|
|
18108
18164
|
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.`
|
|
18165
|
+
`Backfill for source type "${sourceRow.sourceType}" is not implemented yet \u2014 only cloud-run and wordpress are supported in v1.`
|
|
18120
18166
|
);
|
|
18121
18167
|
}
|
|
18122
18168
|
const requestedDays = request.body?.days ?? DEFAULT_BACKFILL_DAYS;
|
|
@@ -18124,13 +18170,86 @@ async function trafficRoutes(app, opts) {
|
|
|
18124
18170
|
throw validationError('"days" must be a positive integer');
|
|
18125
18171
|
}
|
|
18126
18172
|
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
18173
|
const windowEnd = /* @__PURE__ */ new Date();
|
|
18132
18174
|
const windowStart = new Date(windowEnd.getTime() - appliedDays * 864e5);
|
|
18133
18175
|
windowStart.setUTCMinutes(0, 0, 0);
|
|
18176
|
+
let pullForBackfill;
|
|
18177
|
+
let pullErrorPrefix;
|
|
18178
|
+
if (sourceRow.sourceType === TrafficSourceTypes["cloud-run"]) {
|
|
18179
|
+
const credentialStore = opts.cloudRunCredentialStore;
|
|
18180
|
+
if (!credentialStore) {
|
|
18181
|
+
throw validationError("Cloud Run credential storage is not configured for this deployment");
|
|
18182
|
+
}
|
|
18183
|
+
const credential = credentialStore.getConnection(project.name);
|
|
18184
|
+
if (!credential) {
|
|
18185
|
+
throw validationError(
|
|
18186
|
+
`No Cloud Run credential found for project "${project.name}". Run "canonry traffic connect cloud-run" first.`
|
|
18187
|
+
);
|
|
18188
|
+
}
|
|
18189
|
+
const config = parseSourceConfig(sourceRow);
|
|
18190
|
+
const gcpProjectId = config.gcpProjectId ?? credential.gcpProjectId;
|
|
18191
|
+
const serviceName = config.serviceName ?? credential.serviceName ?? void 0;
|
|
18192
|
+
const location = config.location ?? credential.location ?? void 0;
|
|
18193
|
+
pullErrorPrefix = "Cloud Run pull failed";
|
|
18194
|
+
pullForBackfill = async () => {
|
|
18195
|
+
const accessToken = await resolveAccessToken2(credential);
|
|
18196
|
+
const page = await pullEvents(accessToken, {
|
|
18197
|
+
gcpProjectId,
|
|
18198
|
+
serviceName,
|
|
18199
|
+
location,
|
|
18200
|
+
startTime: windowStart.toISOString(),
|
|
18201
|
+
endTime: windowEnd.toISOString(),
|
|
18202
|
+
pageSize: DEFAULT_PAGE_SIZE3,
|
|
18203
|
+
maxPages: BACKFILL_MAX_PAGES,
|
|
18204
|
+
// Backfill is intentionally `firstSync: false`. We don't want desc
|
|
18205
|
+
// ordering — the in-memory rollup builder handles any order, and the
|
|
18206
|
+
// ring-buffer reseed at the end takes the most-recent IDs from the
|
|
18207
|
+
// dedupedEvents anyway.
|
|
18208
|
+
firstSync: false,
|
|
18209
|
+
orderBy: "timestamp asc"
|
|
18210
|
+
});
|
|
18211
|
+
return page.events;
|
|
18212
|
+
};
|
|
18213
|
+
} else {
|
|
18214
|
+
const credentialStore = opts.wordpressTrafficCredentialStore;
|
|
18215
|
+
if (!credentialStore) {
|
|
18216
|
+
throw validationError("WordPress traffic credential storage is not configured for this deployment");
|
|
18217
|
+
}
|
|
18218
|
+
const credential = credentialStore.getConnection(project.name);
|
|
18219
|
+
if (!credential) {
|
|
18220
|
+
throw validationError(
|
|
18221
|
+
`No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
|
|
18222
|
+
);
|
|
18223
|
+
}
|
|
18224
|
+
const wpPageSize = opts.defaultWordpressPageSize ?? DEFAULT_WP_PAGE_SIZE;
|
|
18225
|
+
pullErrorPrefix = "WordPress pull failed";
|
|
18226
|
+
pullForBackfill = async () => {
|
|
18227
|
+
const collected = [];
|
|
18228
|
+
const windowStartIso = windowStart.toISOString();
|
|
18229
|
+
const windowEndIso = windowEnd.toISOString();
|
|
18230
|
+
let cursor = void 0;
|
|
18231
|
+
for (let page = 0; page < BACKFILL_MAX_PAGES; page += 1) {
|
|
18232
|
+
const pageResult = await pullWordpressEvents({
|
|
18233
|
+
baseUrl: credential.baseUrl,
|
|
18234
|
+
username: credential.username,
|
|
18235
|
+
applicationPassword: credential.applicationPassword,
|
|
18236
|
+
cursor,
|
|
18237
|
+
pageSize: wpPageSize,
|
|
18238
|
+
// Each call fetches a single page; the for-loop drives
|
|
18239
|
+
// continuation. Matches the WP sync path's pattern.
|
|
18240
|
+
maxPages: 1,
|
|
18241
|
+
since: windowStartIso,
|
|
18242
|
+
until: windowEndIso
|
|
18243
|
+
});
|
|
18244
|
+
collected.push(...pageResult.events);
|
|
18245
|
+
const previousCursor = cursor;
|
|
18246
|
+
cursor = pageResult.nextCursor;
|
|
18247
|
+
if (!pageResult.hasMore) break;
|
|
18248
|
+
if (!cursor || cursor === previousCursor) break;
|
|
18249
|
+
}
|
|
18250
|
+
return collected;
|
|
18251
|
+
};
|
|
18252
|
+
}
|
|
18134
18253
|
const startedAt = windowEnd.toISOString();
|
|
18135
18254
|
const runId = crypto20.randomUUID();
|
|
18136
18255
|
app.db.insert(runs).values({
|
|
@@ -18148,15 +18267,10 @@ async function trafficRoutes(app, opts) {
|
|
|
18148
18267
|
runId,
|
|
18149
18268
|
project,
|
|
18150
18269
|
sourceRow,
|
|
18151
|
-
gcpProjectId,
|
|
18152
|
-
serviceName,
|
|
18153
|
-
location,
|
|
18154
|
-
credential,
|
|
18155
18270
|
windowStart,
|
|
18156
18271
|
windowEnd,
|
|
18157
|
-
|
|
18158
|
-
|
|
18159
|
-
resolveAccessToken: resolveAccessToken2
|
|
18272
|
+
pullForBackfill,
|
|
18273
|
+
pullErrorPrefix
|
|
18160
18274
|
}).catch(() => {
|
|
18161
18275
|
});
|
|
18162
18276
|
const response = {
|
|
@@ -22887,7 +23001,8 @@ var JobRunner = class {
|
|
|
22887
23001
|
throw new Error("No providers configured. Add at least one provider API key.");
|
|
22888
23002
|
}
|
|
22889
23003
|
log.info("run.dispatch", { runId, providerCount: activeProviders.length, providers: activeProviders.map((p) => p.adapter.name) });
|
|
22890
|
-
|
|
23004
|
+
const scopedQueryNames = parseJsonColumn(existingRun.queries, null);
|
|
23005
|
+
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
23006
|
const projectCompetitors = this.db.select().from(competitors).where(eq27(competitors.projectId, projectId)).all();
|
|
22892
23007
|
const competitorDomains = projectCompetitors.map((c) => c.domain);
|
|
22893
23008
|
const allDomains = effectiveDomains({
|
|
@@ -23167,7 +23282,8 @@ var JobRunner = class {
|
|
|
23167
23282
|
status: runs.status,
|
|
23168
23283
|
finishedAt: runs.finishedAt,
|
|
23169
23284
|
error: runs.error,
|
|
23170
|
-
trigger: runs.trigger
|
|
23285
|
+
trigger: runs.trigger,
|
|
23286
|
+
queries: runs.queries
|
|
23171
23287
|
}).from(runs).where(eq27(runs.id, runId)).get();
|
|
23172
23288
|
}
|
|
23173
23289
|
isRunCancelled(runId) {
|
|
@@ -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) {
|