@canonry/canonry 5.18.0 → 5.19.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/assets/agent-workspace/skills/aero/SKILL.md +13 -8
- package/assets/agent-workspace/skills/aero/references/portfolio-analysis.md +27 -10
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +4 -1
- package/assets/assets/{AuditHistoryPanel-Di5c6xSa.js → AuditHistoryPanel-ddq5tdlB.js} +1 -1
- package/assets/assets/{BacklinksPage-CZi3sZ-7.js → BacklinksPage-g57_xzTX.js} +1 -1
- package/assets/assets/{HistoryPage-BY7HiZWU.js → HistoryPage-C-TdCUQ2.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-D3AdBHpI.js → MeasurementPropertyPage-CAADmNfw.js} +1 -1
- package/assets/assets/{ProjectPage-NyUl1dkF.js → ProjectPage-CoBwpHmA.js} +1 -1
- package/assets/assets/{RunRow-cRjiHQbt.js → RunRow-BpG0Zy-x.js} +1 -1
- package/assets/assets/{RunsPage-DZP_kqJs.js → RunsPage-DK3pb1LH.js} +1 -1
- package/assets/assets/{SettingsPage-BqJXYCDc.js → SettingsPage-BruiY8Zh.js} +1 -1
- package/assets/assets/{SiteHealthSection-Brr0C56X.js → SiteHealthSection-De4U11Gv.js} +3 -3
- package/assets/assets/{TrafficPage-k5NPSDur.js → TrafficPage-BVolLXT2.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-BKNzWnxA.js → TrafficSourceDetailPage-CFDb6lPD.js} +1 -1
- package/assets/assets/{extract-error-message-Cnvelll0.js → extract-error-message-Bk1RTjzo.js} +1 -1
- package/assets/assets/index-B--AA1mG.js +86 -0
- package/assets/assets/{react-sigma_core.esm.min-CthGjQKl.js → react-sigma_core.esm.min--QXVK1JS.js} +1 -1
- package/assets/assets/{v2-overview-adapter-q2o2MzmG.js → v2-overview-adapter-CpjJPZsC.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-Y5IDGH5B.js → chunk-3IAIW5KH.js} +534 -136
- package/dist/chunk-4WROKFN6.js +3991 -0
- package/dist/{chunk-TMTO2JHU.js → chunk-EJXDABGU.js} +21 -3970
- package/dist/{chunk-HIFBIDOW.js → chunk-EJZFTNZG.js} +444 -185
- package/dist/{chunk-X3GFCKUJ.js → chunk-J3JXJIST.js} +3 -3
- package/dist/{chunk-HAABFHXB.js → chunk-PGOID3LE.js} +2 -2
- package/dist/{chunk-77VHT3JD.js → chunk-SOCYTKTF.js} +223 -23
- package/dist/cli.js +84 -27
- package/dist/{demo-server-JBGYZKAI.js → demo-server-BLDYKCFS.js} +1071 -187
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-EORTVFDF.js → intelligence-service-3MHYLKMS.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +9 -9
- package/assets/assets/index-Cd_1GknE.js +0 -86
- package/dist/chunk-AOJU5LKX.js +0 -8
|
@@ -3,29 +3,29 @@ import {
|
|
|
3
3
|
CANONRY_MCP_TIERS,
|
|
4
4
|
CANONRY_MCP_TOOLKITS,
|
|
5
5
|
CANONRY_MCP_TOOLKIT_NAMES,
|
|
6
|
-
CanonryMcpToolNames,
|
|
7
|
-
CliError,
|
|
8
|
-
canonryMcpTools,
|
|
9
6
|
configExists,
|
|
10
7
|
createCanonryMcpServer,
|
|
11
8
|
dashboardManagedRunKindsSchema,
|
|
12
9
|
getBundledSkillSnapshots,
|
|
13
10
|
getConfigPath,
|
|
14
|
-
isMachineFormat,
|
|
15
11
|
loadConfig,
|
|
16
12
|
loadConfigRaw,
|
|
17
13
|
resolveOperatorApiKeyIds,
|
|
18
14
|
runWithUsageTags,
|
|
19
15
|
saveConfigPatch
|
|
20
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-EJXDABGU.js";
|
|
21
17
|
import {
|
|
22
18
|
deleteSiteCrawlGraphLayout,
|
|
23
19
|
persistSiteCrawlGraphLayout,
|
|
24
20
|
prepareSiteCrawlGraphLayout
|
|
25
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-PGOID3LE.js";
|
|
26
22
|
import {
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
CanonryMcpToolNames,
|
|
24
|
+
CliError,
|
|
25
|
+
PACKAGE_VERSION,
|
|
26
|
+
canonryMcpTools,
|
|
27
|
+
isMachineFormat
|
|
28
|
+
} from "./chunk-4WROKFN6.js";
|
|
29
29
|
import {
|
|
30
30
|
CC_CACHE_DIR,
|
|
31
31
|
DUCKDB_SPEC,
|
|
@@ -134,6 +134,8 @@ import {
|
|
|
134
134
|
llmUsageEvents,
|
|
135
135
|
loadDuckdb,
|
|
136
136
|
markSessionFailed,
|
|
137
|
+
measurementPlanVersions,
|
|
138
|
+
measurementPlans,
|
|
137
139
|
measurementRunCompleteness,
|
|
138
140
|
measurementRunSlotState,
|
|
139
141
|
measurementSlotKey,
|
|
@@ -184,7 +186,7 @@ import {
|
|
|
184
186
|
siteLivenessState,
|
|
185
187
|
toAlertView,
|
|
186
188
|
usageCounters
|
|
187
|
-
} from "./chunk-
|
|
189
|
+
} from "./chunk-3IAIW5KH.js";
|
|
188
190
|
import {
|
|
189
191
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
190
192
|
AGENT_NONE,
|
|
@@ -213,6 +215,7 @@ import {
|
|
|
213
215
|
GtmSnapshotKinds,
|
|
214
216
|
LLM_CAPABILITIES,
|
|
215
217
|
LlmCapabilities,
|
|
218
|
+
MEASUREMENT_PLAN_V2_SCHEMA_VERSION,
|
|
216
219
|
MemorySources,
|
|
217
220
|
ONBOARDING_FLOW_VERSION,
|
|
218
221
|
ProviderNames,
|
|
@@ -347,7 +350,7 @@ import {
|
|
|
347
350
|
visibilityReportRequestSchema,
|
|
348
351
|
winnabilityClassLabel,
|
|
349
352
|
withRetry
|
|
350
|
-
} from "./chunk-
|
|
353
|
+
} from "./chunk-SOCYTKTF.js";
|
|
351
354
|
|
|
352
355
|
// src/runtime-context.ts
|
|
353
356
|
function cliRuntimeContext(env = process.env, stdio = process) {
|
|
@@ -3566,7 +3569,7 @@ import crypto27 from "crypto";
|
|
|
3566
3569
|
import fs8 from "fs";
|
|
3567
3570
|
import path8 from "path";
|
|
3568
3571
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3569
|
-
import { and as
|
|
3572
|
+
import { and as and21, eq as eq27 } from "drizzle-orm";
|
|
3570
3573
|
import Fastify from "fastify";
|
|
3571
3574
|
import os4 from "os";
|
|
3572
3575
|
|
|
@@ -6498,7 +6501,7 @@ function nonBlank(value) {
|
|
|
6498
6501
|
return trimmed ? trimmed : void 0;
|
|
6499
6502
|
}
|
|
6500
6503
|
function resolveBuildCommit(env = process.env) {
|
|
6501
|
-
const embedded = true ? "
|
|
6504
|
+
const embedded = true ? "38f4809c78aa8ea4e0b4ab4eb9dbe5e3894ca3ac" : void 0;
|
|
6502
6505
|
return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
|
|
6503
6506
|
}
|
|
6504
6507
|
function resolveInstanceIdentity(env = process.env) {
|
|
@@ -7444,15 +7447,15 @@ var JobRunner = class {
|
|
|
7444
7447
|
const dispatchable = [];
|
|
7445
7448
|
for (const registered of activeProviders) {
|
|
7446
7449
|
const name = registered.adapter.name;
|
|
7447
|
-
const
|
|
7450
|
+
const count2 = unitsByProvider.get(name)?.length ?? 0;
|
|
7448
7451
|
const scope = `${projectId}:${name}`;
|
|
7449
7452
|
const limit = registered.config.quotaPolicy.maxRequestsPerDay;
|
|
7450
|
-
const quota = reserveDailyQueryQuota(this.db, { scope, period: todayPeriod, count, limit });
|
|
7453
|
+
const quota = reserveDailyQueryQuota(this.db, { scope, period: todayPeriod, count: count2, limit });
|
|
7451
7454
|
if (!quota.reserved) {
|
|
7452
|
-
providerErrors.set(name, `Daily quota exceeded for ${name}: ${quota.used} queries used today, limit is ${limit}. This fill needs ${
|
|
7455
|
+
providerErrors.set(name, `Daily quota exceeded for ${name}: ${quota.used} queries used today, limit is ${limit}. This fill needs ${count2} more.`);
|
|
7453
7456
|
continue;
|
|
7454
7457
|
}
|
|
7455
|
-
providerReservations.set(name, { scope, period: todayPeriod, reserved:
|
|
7458
|
+
providerReservations.set(name, { scope, period: todayPeriod, reserved: count2 });
|
|
7456
7459
|
dispatchable.push(registered);
|
|
7457
7460
|
}
|
|
7458
7461
|
const executionGates = /* @__PURE__ */ new Map();
|
|
@@ -7565,8 +7568,8 @@ var JobRunner = class {
|
|
|
7565
7568
|
const remaining = /* @__PURE__ */ new Map();
|
|
7566
7569
|
for (const slot of state.missing) remaining.set(slot.provider, (remaining.get(slot.provider) ?? 0) + 1);
|
|
7567
7570
|
const providers = {};
|
|
7568
|
-
for (const [provider,
|
|
7569
|
-
providers[provider] = fresh[provider] ?? previous[provider] ?? { message: `${
|
|
7571
|
+
for (const [provider, count2] of remaining) {
|
|
7572
|
+
providers[provider] = fresh[provider] ?? previous[provider] ?? { message: `${count2} expected measurement(s) have not run.` };
|
|
7570
7573
|
}
|
|
7571
7574
|
txDb.update(runs).set({ error: serializeRunError({ providers }) }).where(and2(eq2(runs.id, input.runId), eq2(runs.status, "partial"))).run();
|
|
7572
7575
|
}
|
|
@@ -7735,7 +7738,7 @@ var JobRunner = class {
|
|
|
7735
7738
|
ctx.fill?.onOutcome(providerName, false);
|
|
7736
7739
|
}
|
|
7737
7740
|
}
|
|
7738
|
-
incrementUsage(scope, metric,
|
|
7741
|
+
incrementUsage(scope, metric, count2) {
|
|
7739
7742
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7740
7743
|
const period = now.slice(0, 10);
|
|
7741
7744
|
this.db.insert(usageCounters).values({
|
|
@@ -7743,11 +7746,11 @@ var JobRunner = class {
|
|
|
7743
7746
|
scope,
|
|
7744
7747
|
period,
|
|
7745
7748
|
metric,
|
|
7746
|
-
count,
|
|
7749
|
+
count: count2,
|
|
7747
7750
|
updatedAt: now
|
|
7748
7751
|
}).onConflictDoUpdate({
|
|
7749
7752
|
target: [usageCounters.scope, usageCounters.period, usageCounters.metric],
|
|
7750
|
-
set: { count: sql2`${usageCounters.count} + ${
|
|
7753
|
+
set: { count: sql2`${usageCounters.count} + ${count2}`, updatedAt: now }
|
|
7751
7754
|
}).run();
|
|
7752
7755
|
}
|
|
7753
7756
|
flushProviderUsage(providerDispatchCounts, providerReservations) {
|
|
@@ -12074,7 +12077,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
12074
12077
|
return result2;
|
|
12075
12078
|
}
|
|
12076
12079
|
async function backfillInsightsCommand(project, opts) {
|
|
12077
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
12080
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-3MHYLKMS.js");
|
|
12078
12081
|
const config = loadConfig();
|
|
12079
12082
|
const db = createClient(config.database);
|
|
12080
12083
|
migrate(db);
|
|
@@ -12588,8 +12591,8 @@ Traffic classification ${result2.dryRun ? "preview" : "recovery"} complete.`);
|
|
|
12588
12591
|
if (result2.reclassified > 0) {
|
|
12589
12592
|
console.log(` By bot:`);
|
|
12590
12593
|
const sorted = Object.entries(result2.byBot).sort(([, a], [, b]) => b - a);
|
|
12591
|
-
for (const [bot,
|
|
12592
|
-
console.log(` ${
|
|
12594
|
+
for (const [bot, count2] of sorted) {
|
|
12595
|
+
console.log(` ${count2.toString().padStart(5)} ${bot}`);
|
|
12593
12596
|
}
|
|
12594
12597
|
}
|
|
12595
12598
|
if (result2.dryRun) {
|
|
@@ -13928,7 +13931,7 @@ function analysisResultFromInsights(insights2) {
|
|
|
13928
13931
|
|
|
13929
13932
|
// src/agent/session-registry.ts
|
|
13930
13933
|
import crypto24 from "crypto";
|
|
13931
|
-
import { eq as
|
|
13934
|
+
import { eq as eq21 } from "drizzle-orm";
|
|
13932
13935
|
|
|
13933
13936
|
// src/agent/session.ts
|
|
13934
13937
|
import fs7 from "fs";
|
|
@@ -14301,12 +14304,26 @@ import { Type as Type3 } from "@sinclair/typebox";
|
|
|
14301
14304
|
import { Type as Type2 } from "@sinclair/typebox";
|
|
14302
14305
|
import { randomUUID } from "crypto";
|
|
14303
14306
|
var MAX_TOOL_RESULT_CHARS = 2e4;
|
|
14304
|
-
var TRUNCATION_NOTE = "... (truncated
|
|
14307
|
+
var TRUNCATION_NOTE = "... (truncated, result too large)";
|
|
14308
|
+
var TRUNCATION_SUMMARY_KEY = "__truncation";
|
|
14305
14309
|
var MAX_STRUCTURED_TRUNCATION_CHARS = 2e6;
|
|
14306
|
-
var
|
|
14310
|
+
var TRUNCATION_PASS_BUDGET_CHARS = 64e6;
|
|
14311
|
+
var MIN_TRUNCATION_PASSES = 32;
|
|
14312
|
+
var MAX_TRUNCATION_PASSES = 200;
|
|
14313
|
+
var MAX_SUMMARY_DEPTH = 5;
|
|
14314
|
+
var MAX_SUMMARY_KEYS = 30;
|
|
14315
|
+
var MAX_SUMMARY_SEGMENT_CHARS = 60;
|
|
14307
14316
|
function serializeResult(value) {
|
|
14308
14317
|
return JSON.stringify(value, null, 2);
|
|
14309
14318
|
}
|
|
14319
|
+
function keptSummary(counts) {
|
|
14320
|
+
const summary = { droppedKeys: [], keptItems: {} };
|
|
14321
|
+
for (const [path9, { kept, total }] of counts) {
|
|
14322
|
+
if (kept === 0) summary.droppedKeys.push(path9);
|
|
14323
|
+
summary.keptItems[path9] = `${kept} of ${total}`;
|
|
14324
|
+
}
|
|
14325
|
+
return summary;
|
|
14326
|
+
}
|
|
14310
14327
|
function largestArrayKey(obj) {
|
|
14311
14328
|
let best;
|
|
14312
14329
|
let bestLen = -1;
|
|
@@ -14320,117 +14337,278 @@ function largestArrayKey(obj) {
|
|
|
14320
14337
|
}
|
|
14321
14338
|
return best;
|
|
14322
14339
|
}
|
|
14323
|
-
function
|
|
14324
|
-
|
|
14325
|
-
|
|
14326
|
-
|
|
14340
|
+
function fitsCap(text) {
|
|
14341
|
+
return text.length <= MAX_TOOL_RESULT_CHARS;
|
|
14342
|
+
}
|
|
14343
|
+
function largestFitting(max, fits) {
|
|
14327
14344
|
let low = 0;
|
|
14328
|
-
let high =
|
|
14345
|
+
let high = max;
|
|
14329
14346
|
while (low < high) {
|
|
14330
14347
|
const middle = Math.ceil((low + high) / 2);
|
|
14331
|
-
if (
|
|
14348
|
+
if (fits(middle)) low = middle;
|
|
14332
14349
|
else high = middle - 1;
|
|
14333
14350
|
}
|
|
14334
|
-
return
|
|
14351
|
+
return low;
|
|
14335
14352
|
}
|
|
14336
|
-
function
|
|
14337
|
-
|
|
14338
|
-
const
|
|
14353
|
+
function trimRowsToFit(rows, render) {
|
|
14354
|
+
if (rows.length === 0) return [];
|
|
14355
|
+
const all = rows.slice();
|
|
14356
|
+
if (fitsCap(render(all))) return all;
|
|
14357
|
+
return rows.slice(0, largestFitting(rows.length - 1, (count2) => fitsCap(render(rows.slice(0, count2)))));
|
|
14358
|
+
}
|
|
14359
|
+
function nestedArrayGroups(root) {
|
|
14360
|
+
const groups = /* @__PURE__ */ new Map();
|
|
14361
|
+
const visit = (object, path9) => {
|
|
14339
14362
|
let containsRecordCollection = false;
|
|
14340
14363
|
for (const [key, value] of Object.entries(object)) {
|
|
14364
|
+
if (object === root && key === TRUNCATION_SUMMARY_KEY) continue;
|
|
14365
|
+
const valuePath = path9 ? `${path9}.${key}` : key;
|
|
14341
14366
|
if (Array.isArray(value)) {
|
|
14342
14367
|
let hasNestedRecords = false;
|
|
14343
14368
|
let hasRecordRows = false;
|
|
14344
14369
|
for (const row of value) {
|
|
14345
14370
|
if (row && typeof row === "object" && !Array.isArray(row)) {
|
|
14346
14371
|
hasRecordRows = true;
|
|
14347
|
-
hasNestedRecords = visit(row) || hasNestedRecords;
|
|
14372
|
+
hasNestedRecords = visit(row, `${valuePath}[]`) || hasNestedRecords;
|
|
14348
14373
|
}
|
|
14349
14374
|
}
|
|
14350
14375
|
if (value.length > 0 && !hasNestedRecords) {
|
|
14351
|
-
|
|
14376
|
+
const id = `${Number(hasRecordRows)}:${valuePath}`;
|
|
14377
|
+
const group = groups.get(id) ?? { path: valuePath, members: [], size: 0, hasRecordRows };
|
|
14378
|
+
group.members.push({ owner: object, key, rows: value });
|
|
14379
|
+
group.size += serializeResult(value).length;
|
|
14380
|
+
groups.set(id, group);
|
|
14352
14381
|
}
|
|
14353
14382
|
containsRecordCollection = hasRecordRows || hasNestedRecords || containsRecordCollection;
|
|
14354
14383
|
} else if (value && typeof value === "object") {
|
|
14355
|
-
containsRecordCollection = visit(value) || containsRecordCollection;
|
|
14384
|
+
containsRecordCollection = visit(value, valuePath) || containsRecordCollection;
|
|
14356
14385
|
}
|
|
14357
14386
|
}
|
|
14358
14387
|
return containsRecordCollection;
|
|
14359
14388
|
};
|
|
14360
|
-
visit(root);
|
|
14361
|
-
return
|
|
14389
|
+
visit(root, "");
|
|
14390
|
+
return [...groups.values()].sort(
|
|
14362
14391
|
(left, right) => Number(right.hasRecordRows) - Number(left.hasRecordRows) || right.size - left.size
|
|
14363
14392
|
);
|
|
14364
14393
|
}
|
|
14394
|
+
function memberTrimmer({ owner, key, rows }) {
|
|
14395
|
+
const hadTruncated = Object.hasOwn(owner, "__truncated");
|
|
14396
|
+
const priorTruncated = owner.__truncated;
|
|
14397
|
+
const previous = owner.__omittedRowsByField;
|
|
14398
|
+
const priorOmitted = previous && typeof previous === "object" && !Array.isArray(previous) ? previous : void 0;
|
|
14399
|
+
const priorValue = priorOmitted?.[key];
|
|
14400
|
+
const priorCount = typeof priorValue === "number" ? priorValue : 0;
|
|
14401
|
+
return (count2) => {
|
|
14402
|
+
const kept = Math.min(count2, rows.length);
|
|
14403
|
+
owner[key] = rows.slice(0, kept);
|
|
14404
|
+
const omitted = priorCount + rows.length - kept;
|
|
14405
|
+
if (omitted > 0) {
|
|
14406
|
+
owner.__truncated = true;
|
|
14407
|
+
owner.__omittedRowsByField = { ...priorOmitted, [key]: omitted };
|
|
14408
|
+
return kept;
|
|
14409
|
+
}
|
|
14410
|
+
if (hadTruncated) owner.__truncated = priorTruncated;
|
|
14411
|
+
else delete owner.__truncated;
|
|
14412
|
+
if (previous === void 0) delete owner.__omittedRowsByField;
|
|
14413
|
+
else owner.__omittedRowsByField = previous;
|
|
14414
|
+
return kept;
|
|
14415
|
+
};
|
|
14416
|
+
}
|
|
14417
|
+
function emptiedFloorChars(root) {
|
|
14418
|
+
return JSON.stringify(root, (_key, value) => Array.isArray(value) ? [] : value, 2).length;
|
|
14419
|
+
}
|
|
14365
14420
|
function trimNestedArrays(full) {
|
|
14366
14421
|
const parsed = JSON.parse(full);
|
|
14367
14422
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
14368
14423
|
const copy = parsed;
|
|
14424
|
+
const counts = /* @__PURE__ */ new Map();
|
|
14369
14425
|
copy.__truncated = true;
|
|
14426
|
+
copy[TRUNCATION_SUMMARY_KEY] = keptSummary(counts);
|
|
14427
|
+
if (emptiedFloorChars(copy) > MAX_TOOL_RESULT_CHARS) return void 0;
|
|
14370
14428
|
let out = serializeResult(copy);
|
|
14429
|
+
const passLimit = Math.min(
|
|
14430
|
+
MAX_TRUNCATION_PASSES,
|
|
14431
|
+
Math.max(MIN_TRUNCATION_PASSES, Math.floor(TRUNCATION_PASS_BUDGET_CHARS / full.length))
|
|
14432
|
+
);
|
|
14371
14433
|
let passes = 0;
|
|
14372
|
-
while (out
|
|
14373
|
-
if (++passes >
|
|
14374
|
-
const
|
|
14375
|
-
if (!
|
|
14376
|
-
const
|
|
14377
|
-
const
|
|
14378
|
-
const
|
|
14379
|
-
const
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
omitted[key] = priorCount + rows.length - count;
|
|
14434
|
+
while (!fitsCap(out)) {
|
|
14435
|
+
if (++passes > passLimit) return void 0;
|
|
14436
|
+
const group = nestedArrayGroups(copy).at(0);
|
|
14437
|
+
if (!group) return void 0;
|
|
14438
|
+
const trimmers = group.members.map(memberTrimmer);
|
|
14439
|
+
const total = group.members.reduce((sum, member) => sum + member.rows.length, 0);
|
|
14440
|
+
const longest = group.members.reduce((max, member) => Math.max(max, member.rows.length), 0);
|
|
14441
|
+
const prior = counts.get(group.path) ?? { kept: 0, total: 0 };
|
|
14442
|
+
const keep = (count3, extra2 = 0) => {
|
|
14443
|
+
const kept = trimmers.reduce((sum, trim, index) => sum + trim(index < extra2 ? count3 + 1 : count3), 0);
|
|
14444
|
+
counts.set(group.path, { kept: prior.kept + kept, total: prior.total + total });
|
|
14445
|
+
copy[TRUNCATION_SUMMARY_KEY] = keptSummary(counts);
|
|
14385
14446
|
return serializeResult(copy);
|
|
14386
14447
|
};
|
|
14387
14448
|
out = keep(0);
|
|
14388
|
-
if (out
|
|
14389
|
-
|
|
14390
|
-
|
|
14391
|
-
|
|
14392
|
-
const middle = Math.ceil((low + high) / 2);
|
|
14393
|
-
if (keep(middle).length <= MAX_TOOL_RESULT_CHARS) low = middle;
|
|
14394
|
-
else high = middle - 1;
|
|
14395
|
-
}
|
|
14396
|
-
out = keep(low);
|
|
14449
|
+
if (!fitsCap(out)) continue;
|
|
14450
|
+
const count2 = largestFitting(longest - 1, (n) => fitsCap(keep(n)));
|
|
14451
|
+
const extra = trimmers.length > 1 ? largestFitting(trimmers.length - 1, (n) => fitsCap(keep(count2, n))) : 0;
|
|
14452
|
+
out = keep(count2, extra);
|
|
14397
14453
|
}
|
|
14398
14454
|
return out;
|
|
14399
14455
|
}
|
|
14456
|
+
function shortSegment(key) {
|
|
14457
|
+
return key.length > MAX_SUMMARY_SEGMENT_CHARS ? `${key.slice(0, MAX_SUMMARY_SEGMENT_CHARS)}...` : key;
|
|
14458
|
+
}
|
|
14459
|
+
function childPath(container) {
|
|
14460
|
+
if (container.kind === "array") return `${container.path}[${container.complete}]`;
|
|
14461
|
+
const key = shortSegment(container.key ?? "");
|
|
14462
|
+
return container.path ? `${container.path}.${key}` : key;
|
|
14463
|
+
}
|
|
14464
|
+
function serializedKeys(value) {
|
|
14465
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
14466
|
+
const record = value;
|
|
14467
|
+
if (typeof record.toJSON === "function") return void 0;
|
|
14468
|
+
return Object.keys(record).filter((key) => {
|
|
14469
|
+
const child = record[key];
|
|
14470
|
+
return child !== void 0 && typeof child !== "function" && typeof child !== "symbol";
|
|
14471
|
+
});
|
|
14472
|
+
}
|
|
14473
|
+
function openContainersAt(shown, details) {
|
|
14474
|
+
const stack = [];
|
|
14475
|
+
let inString = false;
|
|
14476
|
+
let escaped = false;
|
|
14477
|
+
let stringStart = 0;
|
|
14478
|
+
for (let i = 0; i < shown.length; i++) {
|
|
14479
|
+
const ch = shown[i];
|
|
14480
|
+
const top = stack.at(-1);
|
|
14481
|
+
if (inString) {
|
|
14482
|
+
if (escaped) escaped = false;
|
|
14483
|
+
else if (ch === "\\") escaped = true;
|
|
14484
|
+
else if (ch === '"') {
|
|
14485
|
+
inString = false;
|
|
14486
|
+
if (top?.kind === "object" && top.expectKey) {
|
|
14487
|
+
top.key = JSON.parse(shown.slice(stringStart, i + 1));
|
|
14488
|
+
top.expectKey = false;
|
|
14489
|
+
}
|
|
14490
|
+
}
|
|
14491
|
+
continue;
|
|
14492
|
+
}
|
|
14493
|
+
if (ch === '"') {
|
|
14494
|
+
inString = true;
|
|
14495
|
+
stringStart = i;
|
|
14496
|
+
} else if (ch === "{" || ch === "[") {
|
|
14497
|
+
let value = details;
|
|
14498
|
+
if (top) {
|
|
14499
|
+
const parent = top.value;
|
|
14500
|
+
value = top.kind === "array" ? Array.isArray(parent) ? parent[top.complete] : void 0 : parent && typeof parent === "object" ? parent[top.key ?? ""] : void 0;
|
|
14501
|
+
}
|
|
14502
|
+
stack.push({
|
|
14503
|
+
kind: ch === "{" ? "object" : "array",
|
|
14504
|
+
path: top ? childPath(top) : "",
|
|
14505
|
+
value,
|
|
14506
|
+
complete: 0,
|
|
14507
|
+
childClosed: false,
|
|
14508
|
+
expectKey: ch === "{"
|
|
14509
|
+
});
|
|
14510
|
+
} else if (ch === "}" || ch === "]") {
|
|
14511
|
+
stack.pop();
|
|
14512
|
+
const parent = stack.at(-1);
|
|
14513
|
+
if (parent) parent.childClosed = true;
|
|
14514
|
+
} else if (ch === "," && top) {
|
|
14515
|
+
top.complete++;
|
|
14516
|
+
top.childClosed = false;
|
|
14517
|
+
top.expectKey = top.kind === "object";
|
|
14518
|
+
}
|
|
14519
|
+
}
|
|
14520
|
+
return stack;
|
|
14521
|
+
}
|
|
14522
|
+
function sliceSummaryLine(shown, details) {
|
|
14523
|
+
if (!shown.startsWith("{") && !shown.startsWith("[")) return "";
|
|
14524
|
+
const stack = openContainersAt(shown, details);
|
|
14525
|
+
const root = stack.at(0);
|
|
14526
|
+
const deepest = stack.at(-1);
|
|
14527
|
+
if (!root || !deepest) return "";
|
|
14528
|
+
const summary = {
|
|
14529
|
+
// Between two children the cut is named by their container.
|
|
14530
|
+
cutAt: deepest.childClosed || deepest.expectKey ? deepest.path : childPath(deepest),
|
|
14531
|
+
droppedKeys: [],
|
|
14532
|
+
keptItems: {}
|
|
14533
|
+
};
|
|
14534
|
+
for (const container of stack.slice(0, MAX_SUMMARY_DEPTH)) {
|
|
14535
|
+
const total = container.kind === "array" ? Array.isArray(container.value) ? container.value.length : void 0 : serializedKeys(container.value)?.length;
|
|
14536
|
+
if (total === void 0) break;
|
|
14537
|
+
if (container === root && container.kind === "object") continue;
|
|
14538
|
+
const shownCount = container.complete + (container.childClosed ? 1 : 0);
|
|
14539
|
+
summary.keptItems[container.path || "(root)"] = container.kind === "array" ? `${shownCount} of ${total}` : `${shownCount} of ${total} keys`;
|
|
14540
|
+
}
|
|
14541
|
+
const rootKeys = root.kind === "object" ? serializedKeys(root.value) : void 0;
|
|
14542
|
+
if (rootKeys) {
|
|
14543
|
+
const inProgress = root.expectKey || root.childClosed ? 0 : 1;
|
|
14544
|
+
const dropped = rootKeys.slice(root.complete + (root.childClosed ? 1 : 0) + inProgress);
|
|
14545
|
+
const record = root.value;
|
|
14546
|
+
for (const key of dropped.slice(0, MAX_SUMMARY_KEYS)) {
|
|
14547
|
+
const label = shortSegment(key);
|
|
14548
|
+
summary.droppedKeys.push(label);
|
|
14549
|
+
const value = record[key];
|
|
14550
|
+
if (Array.isArray(value)) summary.keptItems[label] = `0 of ${value.length}`;
|
|
14551
|
+
}
|
|
14552
|
+
if (dropped.length > MAX_SUMMARY_KEYS) summary.moreDroppedKeys = dropped.length - MAX_SUMMARY_KEYS;
|
|
14553
|
+
}
|
|
14554
|
+
return `${TRUNCATION_SUMMARY_KEY}: ${JSON.stringify(summary)}`;
|
|
14555
|
+
}
|
|
14556
|
+
function markedSlice(full, details) {
|
|
14557
|
+
let cut = MAX_TOOL_RESULT_CHARS;
|
|
14558
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
14559
|
+
const line = sliceSummaryLine(full.slice(0, cut), details);
|
|
14560
|
+
const cost = line ? line.length + 1 : 0;
|
|
14561
|
+
if (cut + cost <= MAX_TOOL_RESULT_CHARS) {
|
|
14562
|
+
return full.slice(0, cut) + "\n" + (line ? line + "\n" : "") + TRUNCATION_NOTE;
|
|
14563
|
+
}
|
|
14564
|
+
cut = MAX_TOOL_RESULT_CHARS - cost - attempt * 16;
|
|
14565
|
+
}
|
|
14566
|
+
return full.slice(0, MAX_TOOL_RESULT_CHARS) + "\n" + TRUNCATION_NOTE;
|
|
14567
|
+
}
|
|
14400
14568
|
function truncateToolResult(details) {
|
|
14401
14569
|
const full = serializeResult(details);
|
|
14402
14570
|
if (full.length <= MAX_TOOL_RESULT_CHARS) return full;
|
|
14403
|
-
if (full.length > MAX_STRUCTURED_TRUNCATION_CHARS)
|
|
14404
|
-
return full.slice(0, MAX_TOOL_RESULT_CHARS) + "\n" + TRUNCATION_NOTE;
|
|
14405
|
-
}
|
|
14571
|
+
if (full.length > MAX_STRUCTURED_TRUNCATION_CHARS) return markedSlice(full, details);
|
|
14406
14572
|
if (Array.isArray(details)) {
|
|
14407
|
-
const
|
|
14408
|
-
|
|
14409
|
-
|
|
14410
|
-
|
|
14411
|
-
|
|
14573
|
+
const render = (rows) => serializeResult({
|
|
14574
|
+
items: rows,
|
|
14575
|
+
__truncated: true,
|
|
14576
|
+
__omittedRows: details.length - rows.length,
|
|
14577
|
+
[TRUNCATION_SUMMARY_KEY]: keptSummary(/* @__PURE__ */ new Map([["items", { kept: rows.length, total: details.length }]]))
|
|
14578
|
+
});
|
|
14579
|
+
return render(trimRowsToFit(details, render));
|
|
14412
14580
|
}
|
|
14413
14581
|
if (details && typeof details === "object") {
|
|
14414
14582
|
const obj = details;
|
|
14415
14583
|
const arrayKey = largestArrayKey(obj);
|
|
14416
14584
|
if (arrayKey) {
|
|
14417
14585
|
const rows = obj[arrayKey];
|
|
14418
|
-
const
|
|
14419
|
-
rows,
|
|
14420
|
-
(r) => serializeResult({ ...obj, [arrayKey]: r, __truncated: true, __omittedRows: rows.length - r.length })
|
|
14421
|
-
);
|
|
14422
|
-
const out = serializeResult({
|
|
14586
|
+
const render = (kept) => serializeResult({
|
|
14423
14587
|
...obj,
|
|
14424
14588
|
[arrayKey]: kept,
|
|
14425
14589
|
__truncated: true,
|
|
14426
|
-
__omittedRows: rows.length - kept.length
|
|
14590
|
+
__omittedRows: rows.length - kept.length,
|
|
14591
|
+
[TRUNCATION_SUMMARY_KEY]: keptSummary(/* @__PURE__ */ new Map([[arrayKey, { kept: kept.length, total: rows.length }]]))
|
|
14427
14592
|
});
|
|
14428
|
-
|
|
14593
|
+
const out = render(trimRowsToFit(rows, render));
|
|
14594
|
+
const otherLists = Object.entries(obj).some(([key, value]) => key !== arrayKey && Array.isArray(value) && value.length > 0);
|
|
14595
|
+
const nested2 = otherLists ? trimNestedArrays(full) : void 0;
|
|
14596
|
+
if (fitsCap(out) && nested2 !== void 0 && keptRows(nested2, arrayKey) > keptRows(out, arrayKey)) return nested2;
|
|
14597
|
+
if (fitsCap(out)) return out;
|
|
14598
|
+
if (nested2 !== void 0) return nested2;
|
|
14429
14599
|
}
|
|
14430
14600
|
const nested = trimNestedArrays(full);
|
|
14431
14601
|
if (nested !== void 0) return nested;
|
|
14432
14602
|
}
|
|
14433
|
-
return full
|
|
14603
|
+
return markedSlice(full, details);
|
|
14604
|
+
}
|
|
14605
|
+
function keptRows(serialized, key) {
|
|
14606
|
+
try {
|
|
14607
|
+
const parsed = JSON.parse(serialized);
|
|
14608
|
+
return Array.isArray(parsed[key]) ? parsed[key].length : 0;
|
|
14609
|
+
} catch {
|
|
14610
|
+
return 0;
|
|
14611
|
+
}
|
|
14434
14612
|
}
|
|
14435
14613
|
function textResult2(details) {
|
|
14436
14614
|
return {
|
|
@@ -14887,6 +15065,7 @@ function splitAeroAnthropicSystemCachePayload(payload, model) {
|
|
|
14887
15065
|
import { z } from "zod";
|
|
14888
15066
|
import { Type as Type4 } from "@sinclair/typebox";
|
|
14889
15067
|
var DISCOVER = "aero_list_toolkits";
|
|
15068
|
+
var MAX_VISIBLE_TOOLS = 128;
|
|
14890
15069
|
var LOAD = "aero_load_toolkit";
|
|
14891
15070
|
var metadata = new Map(canonryMcpTools.map((tool) => [tool.name, tool]));
|
|
14892
15071
|
var runtimes = /* @__PURE__ */ new WeakMap();
|
|
@@ -14897,7 +15076,7 @@ function visibleTools(runtime) {
|
|
|
14897
15076
|
if (!runtime.progressive) return runtime.allowed;
|
|
14898
15077
|
const visible = runtime.allowed.filter((tool) => {
|
|
14899
15078
|
const entry = metadata.get(tool.name);
|
|
14900
|
-
return !entry || entry.tier === "core" || runtime.loaded.has(entry.tier);
|
|
15079
|
+
return !entry || entry.tier === "core" || runtime.pinned.has(tool.name) || runtime.loaded.has(entry.tier);
|
|
14901
15080
|
});
|
|
14902
15081
|
const available = CANONRY_MCP_TOOLKITS.filter((kit) => runtime.allowed.some((tool) => metadata.get(tool.name)?.tier === kit.name));
|
|
14903
15082
|
return [...visible, {
|
|
@@ -14914,15 +15093,39 @@ function visibleTools(runtime) {
|
|
|
14914
15093
|
execute: async (_id, input) => {
|
|
14915
15094
|
const args = z.object({ toolkit: z.string() }).parse(input);
|
|
14916
15095
|
if (!available.some((kit) => kit.name === args.toolkit)) throw new Error("Toolkit is unavailable for this turn.");
|
|
15096
|
+
runtime.loaded.delete(args.toolkit);
|
|
14917
15097
|
runtime.loaded.add(args.toolkit);
|
|
14918
|
-
|
|
15098
|
+
const unloaded = [];
|
|
15099
|
+
for (const older of [...runtime.loaded]) {
|
|
15100
|
+
if (visibleTools(runtime).length <= MAX_VISIBLE_TOOLS || older === args.toolkit) continue;
|
|
15101
|
+
runtime.loaded.delete(older);
|
|
15102
|
+
unloaded.push(older);
|
|
15103
|
+
}
|
|
15104
|
+
return result({
|
|
15105
|
+
tools: runtime.allowed.filter((tool) => metadata.get(tool.name)?.tier === args.toolkit).map((tool) => ({ name: tool.name, description: tool.description })),
|
|
15106
|
+
...unloaded.length > 0 ? { unloaded, note: `Unloaded ${unloaded.join(", ")} to stay within the tool limit; load again if needed.` } : {}
|
|
15107
|
+
});
|
|
14919
15108
|
}
|
|
14920
15109
|
}];
|
|
14921
15110
|
}
|
|
14922
|
-
function
|
|
15111
|
+
function explainMissingTool(runtime, message) {
|
|
15112
|
+
if (!message.isError || !Array.isArray(message.content)) return;
|
|
15113
|
+
const first = message.content[0];
|
|
15114
|
+
const name = first?.type === "text" ? /^Tool (\S+) not found$/.exec(first.text ?? "")?.[1] : void 0;
|
|
15115
|
+
if (!name) return;
|
|
15116
|
+
let text;
|
|
15117
|
+
if (!runtime.progressive) {
|
|
15118
|
+
text = `${name} is not available in this conversation. Use only the tools listed for you.`;
|
|
15119
|
+
} else {
|
|
15120
|
+
const toolkit = metadata.get(name)?.tier;
|
|
15121
|
+
text = runtime.allowed.some((tool) => tool.name === name) && toolkit && toolkit !== "core" ? `${name} is not loaded yet. Call ${LOAD} with toolkit "${toolkit}", then call ${name} again.` : `${name} is not available in this conversation. Use ${DISCOVER} to see the tools you can load.`;
|
|
15122
|
+
}
|
|
15123
|
+
message.content = [{ type: "text", text }];
|
|
15124
|
+
}
|
|
15125
|
+
function configureAeroRuntime(agent, allowed, limits, progressive = true, pinned = []) {
|
|
14923
15126
|
let runtime = runtimes.get(agent);
|
|
14924
15127
|
if (!runtime) {
|
|
14925
|
-
runtime = { allowed, loaded: /* @__PURE__ */ new Set(), limits: agentTurnLimitsSchema.parse(limits ?? {}), calls: 0, rounds: 0, startedAt: 0, reason: "completed", progressive };
|
|
15128
|
+
runtime = { allowed, loaded: /* @__PURE__ */ new Set(), limits: agentTurnLimitsSchema.parse(limits ?? {}), calls: 0, rounds: 0, startedAt: 0, reason: "completed", progressive, pinned: new Set(pinned) };
|
|
14926
15129
|
runtimes.set(agent, runtime);
|
|
14927
15130
|
const state = runtime;
|
|
14928
15131
|
const stream = agent.streamFn;
|
|
@@ -14959,6 +15162,7 @@ function configureAeroRuntime(agent, allowed, limits, progressive = true) {
|
|
|
14959
15162
|
}
|
|
14960
15163
|
if (event.type === "message_end" && event.message.role === "toolResult") {
|
|
14961
15164
|
const message = event.message;
|
|
15165
|
+
explainMissingTool(state, message);
|
|
14962
15166
|
const startedAt = toolStarts.get(message.toolCallId);
|
|
14963
15167
|
Object.assign(message, {
|
|
14964
15168
|
aeroToolLabel: state.allowed.find((tool) => tool.name === message.toolName)?.label,
|
|
@@ -14990,6 +15194,7 @@ function configureAeroRuntime(agent, allowed, limits, progressive = true) {
|
|
|
14990
15194
|
runtime.loaded = /* @__PURE__ */ new Set();
|
|
14991
15195
|
runtime.limits = agentTurnLimitsSchema.parse(limits ?? {});
|
|
14992
15196
|
runtime.progressive = progressive;
|
|
15197
|
+
runtime.pinned = new Set(pinned);
|
|
14993
15198
|
agent.state.tools = visibleTools(runtime);
|
|
14994
15199
|
}
|
|
14995
15200
|
function aeroTurnStatus(agent) {
|
|
@@ -15036,7 +15241,7 @@ function buildAeroViewTool(options, validatedEvidence) {
|
|
|
15036
15241
|
}
|
|
15037
15242
|
};
|
|
15038
15243
|
}
|
|
15039
|
-
var AERO_RUNTIME_PROMPT = "\n\nNative Aero tools: when present, use aero_list_toolkits and aero_load_toolkit to discover and load task-specific tools before calling them. Otherwise use the exposed catalog. Start view-relative analysis with aero_inspect_view when available. Cite the returned source.href beside claims. Name the measurement date, class, numerator/denominator, and comparison limitations. Separate observations from possible explanations; do not infer causation. Never turn missing, incomplete, or truncated evidence into zero. Without current view context, resolve ambiguous references before making scoped claims.";
|
|
15244
|
+
var AERO_RUNTIME_PROMPT = "\n\nNative Aero tools: when present, use aero_list_toolkits and aero_load_toolkit to discover and load task-specific tools before calling them. Otherwise use the exposed catalog. Start view-relative analysis with aero_inspect_view when available. Cite the returned source.href beside claims. Name the measurement date, class, numerator/denominator, and comparison limitations. Separate observations from possible explanations; do not infer causation. Never turn missing, incomplete, or truncated evidence into zero. A tool result that says it was truncated is partial: never list, rank, count or group items you did not see; say what was cut. Without current view context, resolve ambiguous references before making scoped claims.";
|
|
15040
15245
|
function aeroViewPrompt(context) {
|
|
15041
15246
|
return context ? `
|
|
15042
15247
|
|
|
@@ -15460,6 +15665,59 @@ function writeCompactionNote(db, args) {
|
|
|
15460
15665
|
return inserted;
|
|
15461
15666
|
}
|
|
15462
15667
|
|
|
15668
|
+
// src/agent/project-shape.ts
|
|
15669
|
+
import { and as and17, count, eq as eq20 } from "drizzle-orm";
|
|
15670
|
+
var ADVANCED_TOOLS = [
|
|
15671
|
+
"canonry_measurement_portfolio_summary",
|
|
15672
|
+
"canonry_measurement_overview",
|
|
15673
|
+
"canonry_measurement_property_evidence",
|
|
15674
|
+
"canonry_measurement_property_competitors",
|
|
15675
|
+
"canonry_measurement_changes",
|
|
15676
|
+
"canonry_visibility_report",
|
|
15677
|
+
"canonry_analytics_sources"
|
|
15678
|
+
];
|
|
15679
|
+
var LEGACY_TOOLS = ["canonry_measurement_overview", "canonry_measurement_plan_get"];
|
|
15680
|
+
var SIMPLE_TOOLS = ["canonry_visibility_report"];
|
|
15681
|
+
function aeroProjectShape(db, projectId) {
|
|
15682
|
+
try {
|
|
15683
|
+
const pointer = db.select().from(measurementPlans).where(eq20(measurementPlans.projectId, projectId)).get();
|
|
15684
|
+
const version = pointer ? db.select().from(measurementPlanVersions).where(and17(
|
|
15685
|
+
eq20(measurementPlanVersions.projectId, projectId),
|
|
15686
|
+
eq20(measurementPlanVersions.id, pointer.activeVersionId)
|
|
15687
|
+
)).get() : void 0;
|
|
15688
|
+
if (version) {
|
|
15689
|
+
const plan = JSON.parse(version.canonicalJson);
|
|
15690
|
+
const groups = plan.groups ?? [];
|
|
15691
|
+
const topLevel = groups.filter((group) => !group.parentGroupKey).length;
|
|
15692
|
+
const size = `${plan.targets?.length ?? 0} Properties in ${groups.length} groups (${topLevel} top-level, ${groups.length - topLevel} nested)`;
|
|
15693
|
+
if ((plan.schemaVersion ?? version.schemaVersion) !== MEASUREMENT_PLAN_V2_SCHEMA_VERSION) {
|
|
15694
|
+
return {
|
|
15695
|
+
prompt: `
|
|
15696
|
+
|
|
15697
|
+
Project shape: an Advanced Measurement plan on the legacy schema v1 (plan revision ${version.revision}) with ${size}. Its queries are not classified as branded or non-brand, so do not report class splits for it. Read it with canonry_measurement_overview and canonry_measurement_plan_get; the portfolio ranking, Property evidence and the advanced visibility report need a schema-v2 plan and will refuse this one.`,
|
|
15698
|
+
pinned: LEGACY_TOOLS
|
|
15699
|
+
};
|
|
15700
|
+
}
|
|
15701
|
+
const queriesIn = (queryClass) => new Set((plan.assignments ?? []).filter((assignment) => assignment.queryClass === queryClass).map((assignment) => assignment.queryId)).size;
|
|
15702
|
+
return {
|
|
15703
|
+
prompt: `
|
|
15704
|
+
|
|
15705
|
+
Project shape: an Advanced Measurement portfolio (plan revision ${version.revision}) with ${size}, ${queriesIn("branded")} branded and ${queriesIn("non-brand")} non-brand queries. Measure per Property, group or market, never pool branded and non-brand, and a query shared by several Properties is one answer scored for each. Start portfolio questions with canonry_measurement_portfolio_summary (weakest-first ranking with each Property's metro, the names answers wrote instead, and the domains they cited) or canonry_measurement_overview; drill into one Property with canonry_measurement_property_evidence and canonry_measurement_property_competitors; compare sweeps with canonry_measurement_changes. Project-wide tools such as canonry_visibility_report describe the whole project, not one Property. Group Properties only by the metro and submarkets the tools return, never by name. Names given instead were written in the answer text, not cited. Denominators count answers (queries x engines), not queries. Properties tied at the weakest rate are not ranked against each other. For sources, use each Property's citedDomains in the portfolio summary, or canonry_analytics_sources with queryClass and runId set; without them it pools both classes and every sweep. canonry_measurement_plan_get is plan structure with no metrics; do not read it for analysis.`,
|
|
15706
|
+
pinned: ADVANCED_TOOLS
|
|
15707
|
+
};
|
|
15708
|
+
}
|
|
15709
|
+
const tracked = db.select({ total: count() }).from(queries).where(eq20(queries.projectId, projectId)).get()?.total ?? 0;
|
|
15710
|
+
return {
|
|
15711
|
+
prompt: `
|
|
15712
|
+
|
|
15713
|
+
Project shape: a Simple project with ${tracked} tracked queries and no measurement plan (no Properties or markets). Start with canonry_project_overview, then canonry_visibility_report for mention and citation rates by query and engine.`,
|
|
15714
|
+
pinned: SIMPLE_TOOLS
|
|
15715
|
+
};
|
|
15716
|
+
} catch {
|
|
15717
|
+
return { prompt: "", pinned: [] };
|
|
15718
|
+
}
|
|
15719
|
+
}
|
|
15720
|
+
|
|
15463
15721
|
// src/agent/compaction.ts
|
|
15464
15722
|
import { complete } from "@mariozechner/pi-ai";
|
|
15465
15723
|
|
|
@@ -15702,7 +15960,7 @@ var SessionRegistry = class {
|
|
|
15702
15960
|
modelProvider: effectiveProvider,
|
|
15703
15961
|
modelId: effectiveModelId,
|
|
15704
15962
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15705
|
-
}).where(
|
|
15963
|
+
}).where(eq21(agentSessions.projectId, projectId)).run();
|
|
15706
15964
|
}
|
|
15707
15965
|
const agent2 = createAeroSession({
|
|
15708
15966
|
projectName,
|
|
@@ -15869,9 +16127,10 @@ ${lines.join("\n")}
|
|
|
15869
16127
|
preferences?.signal?.throwIfAborted();
|
|
15870
16128
|
await this.maybeCompact(projectName, agent);
|
|
15871
16129
|
preferences?.signal?.throwIfAborted();
|
|
15872
|
-
agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, systemPrompt) + aeroViewPrompt(preferences?.context);
|
|
15873
16130
|
const progressive = (preferences?.toolProfile ?? AeroToolProfiles.default) === AeroToolProfiles.default;
|
|
15874
|
-
|
|
16131
|
+
const shape = aeroProjectShape(this.opts.db, projectId);
|
|
16132
|
+
agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, systemPrompt) + shape.prompt + aeroViewPrompt(preferences?.context);
|
|
16133
|
+
configureAeroRuntime(agent, [...agent.state.tools, ...progressive ? [buildAeroViewTool(view, evidence)] : []], preferences?.limits, progressive, shape.pinned);
|
|
15875
16134
|
return agent;
|
|
15876
16135
|
} finally {
|
|
15877
16136
|
this.acquisitions.delete(projectName);
|
|
@@ -15997,7 +16256,7 @@ ${lines.join("\n")}
|
|
|
15997
16256
|
modelProvider: nextProvider,
|
|
15998
16257
|
modelId: nextModelId,
|
|
15999
16258
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16000
|
-
}).where(
|
|
16259
|
+
}).where(eq21(agentSessions.projectId, projectId)).run();
|
|
16001
16260
|
}
|
|
16002
16261
|
/** Snapshot pending work without consuming it before an atomic conversation switch. */
|
|
16003
16262
|
prepareConversationChange(projectName) {
|
|
@@ -16171,11 +16430,11 @@ ${lines.join("\n")}
|
|
|
16171
16430
|
return id;
|
|
16172
16431
|
}
|
|
16173
16432
|
tryResolveProjectId(projectName) {
|
|
16174
|
-
const row = this.opts.db.select({ id: projects.id }).from(projects).where(
|
|
16433
|
+
const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq21(projects.name, projectName)).get();
|
|
16175
16434
|
return row?.id;
|
|
16176
16435
|
}
|
|
16177
16436
|
loadRow(projectId) {
|
|
16178
|
-
const row = this.opts.db.select().from(agentSessions).where(
|
|
16437
|
+
const row = this.opts.db.select().from(agentSessions).where(eq21(agentSessions.projectId, projectId)).get();
|
|
16179
16438
|
return row ?? null;
|
|
16180
16439
|
}
|
|
16181
16440
|
insertRow(params) {
|
|
@@ -16194,7 +16453,7 @@ ${lines.join("\n")}
|
|
|
16194
16453
|
}
|
|
16195
16454
|
updateRow(projectId, patch) {
|
|
16196
16455
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16197
|
-
this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(
|
|
16456
|
+
this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq21(agentSessions.projectId, projectId)).run();
|
|
16198
16457
|
}
|
|
16199
16458
|
/**
|
|
16200
16459
|
* Store a refreshed prompt snapshot without touching `updatedAt`, which the
|
|
@@ -16203,13 +16462,13 @@ ${lines.join("\n")}
|
|
|
16203
16462
|
* recently used right after an upgrade.
|
|
16204
16463
|
*/
|
|
16205
16464
|
persistPromptSnapshot(projectId, systemPrompt) {
|
|
16206
|
-
this.opts.db.update(agentSessions).set({ systemPrompt }).where(
|
|
16465
|
+
this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq21(agentSessions.projectId, projectId)).run();
|
|
16207
16466
|
}
|
|
16208
16467
|
};
|
|
16209
16468
|
|
|
16210
16469
|
// src/agent/viewer-sessions.ts
|
|
16211
16470
|
import crypto25 from "crypto";
|
|
16212
|
-
import { and as
|
|
16471
|
+
import { and as and18, eq as eq22, isNotNull, like as like2 } from "drizzle-orm";
|
|
16213
16472
|
var AERO_VIEWER_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
|
|
16214
16473
|
CanonryMcpToolNames.canonry_ads_account,
|
|
16215
16474
|
CanonryMcpToolNames.canonry_ads_geo_search,
|
|
@@ -16239,7 +16498,6 @@ var AERO_VIEWER_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
|
|
|
16239
16498
|
var OPERATOR_ONLY_TOOL_NAMES = new Set(
|
|
16240
16499
|
canonryMcpTools.filter((tool) => tool.tier === "agent" || tool.requiresOperator).map((tool) => tool.name)
|
|
16241
16500
|
);
|
|
16242
|
-
var VIEWER_AERO_TURN_LIMITS = { maxToolCalls: 12, timeoutMs: 12e4 };
|
|
16243
16501
|
var VIEWER_AERO_MAX_PROMPT_CHARS = 4e3;
|
|
16244
16502
|
var VIEWER_AERO_DAILY_TURN_LIMIT = 50;
|
|
16245
16503
|
function viewerAeroDailyLimitExceeded(limit) {
|
|
@@ -16323,8 +16581,8 @@ var ViewerAeroSessions = class {
|
|
|
16323
16581
|
if (this.isBusy(project.name, userId)) throw agentBusy(project.name);
|
|
16324
16582
|
const today = new Date(this.now()).toISOString().slice(0, 10);
|
|
16325
16583
|
const used = this.turnsToday.get(key);
|
|
16326
|
-
const
|
|
16327
|
-
if (
|
|
16584
|
+
const count2 = used?.date === today ? used.count : 0;
|
|
16585
|
+
if (count2 >= VIEWER_AERO_DAILY_TURN_LIMIT) throw viewerAeroDailyLimitExceeded(VIEWER_AERO_DAILY_TURN_LIMIT);
|
|
16328
16586
|
const minted = mintViewerAeroKey(this.opts.db, userId, new Date(this.now()));
|
|
16329
16587
|
this.acquisitions.add(key);
|
|
16330
16588
|
let handedOff = false;
|
|
@@ -16335,10 +16593,11 @@ var ViewerAeroSessions = class {
|
|
|
16335
16593
|
const view = { client, projectName: project.name, basePath: this.opts.config.basePath, context: preferences.context };
|
|
16336
16594
|
const evidence = preferences.context ? await readAeroViewEvidence(view) : void 0;
|
|
16337
16595
|
preferences.signal?.throwIfAborted();
|
|
16338
|
-
|
|
16339
|
-
|
|
16596
|
+
const shape = aeroProjectShape(this.opts.db, project.id);
|
|
16597
|
+
agent.state.systemPrompt = loadAeroSystemPrompt(void 0, { extras: false }) + VIEWER_AERO_PROMPT + shape.prompt + aeroViewPrompt(preferences.context);
|
|
16598
|
+
configureAeroRuntime(agent, [...buildViewerAeroTools(client, project.name, this.opts.managedSweeps), buildAeroViewTool(view, evidence)], void 0, true, shape.pinned);
|
|
16340
16599
|
this.sessions.set(key, { agent, lastUsedAt: this.now(), updatedAt: this.sessions.get(key)?.updatedAt ?? null });
|
|
16341
|
-
this.turnsToday.set(key, { date: today, count:
|
|
16600
|
+
this.turnsToday.set(key, { date: today, count: count2 + 1 });
|
|
16342
16601
|
handedOff = true;
|
|
16343
16602
|
let released = false;
|
|
16344
16603
|
return {
|
|
@@ -16385,10 +16644,10 @@ var ViewerAeroSessions = class {
|
|
|
16385
16644
|
*/
|
|
16386
16645
|
removeKey(id) {
|
|
16387
16646
|
try {
|
|
16388
|
-
this.opts.db.delete(apiKeys).where(
|
|
16647
|
+
this.opts.db.delete(apiKeys).where(eq22(apiKeys.id, id)).run();
|
|
16389
16648
|
} catch {
|
|
16390
16649
|
try {
|
|
16391
|
-
this.opts.db.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(
|
|
16650
|
+
this.opts.db.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(eq22(apiKeys.id, id)).run();
|
|
16392
16651
|
} catch {
|
|
16393
16652
|
}
|
|
16394
16653
|
}
|
|
@@ -16400,7 +16659,7 @@ var ViewerAeroSessions = class {
|
|
|
16400
16659
|
*/
|
|
16401
16660
|
revokeOrphanedKeys() {
|
|
16402
16661
|
try {
|
|
16403
|
-
this.opts.db.delete(apiKeys).where(
|
|
16662
|
+
this.opts.db.delete(apiKeys).where(and18(like2(apiKeys.name, `${VIEWER_AERO_KEY_PREFIX}%`), isNotNull(apiKeys.delegatedUserId))).run();
|
|
16404
16663
|
} catch {
|
|
16405
16664
|
}
|
|
16406
16665
|
}
|
|
@@ -16416,7 +16675,7 @@ var ViewerAeroSessions = class {
|
|
|
16416
16675
|
|
|
16417
16676
|
// src/mcp-http.ts
|
|
16418
16677
|
import crypto26 from "crypto";
|
|
16419
|
-
import { eq as
|
|
16678
|
+
import { eq as eq23 } from "drizzle-orm";
|
|
16420
16679
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
16421
16680
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
16422
16681
|
var SESSION_IDLE_MS = 10 * 60 * 1e3;
|
|
@@ -16437,7 +16696,7 @@ function mintSessionKey(db, scopes, userId) {
|
|
|
16437
16696
|
}
|
|
16438
16697
|
function revokeSessionKey(db, id) {
|
|
16439
16698
|
try {
|
|
16440
|
-
db.update(apiKeys).set({ revokedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
16699
|
+
db.update(apiKeys).set({ revokedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq23(apiKeys.id, id)).run();
|
|
16441
16700
|
} catch {
|
|
16442
16701
|
}
|
|
16443
16702
|
}
|
|
@@ -16620,7 +16879,7 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
16620
16879
|
}
|
|
16621
16880
|
|
|
16622
16881
|
// src/agent/agent-routes.ts
|
|
16623
|
-
import { eq as
|
|
16882
|
+
import { eq as eq24 } from "drizzle-orm";
|
|
16624
16883
|
function viewerAeroCaller(request, opts) {
|
|
16625
16884
|
if (!opts.viewerSessions) return null;
|
|
16626
16885
|
const principal = request.principal;
|
|
@@ -16650,7 +16909,7 @@ function viewerTranscript(opts, projectName, userId) {
|
|
|
16650
16909
|
};
|
|
16651
16910
|
}
|
|
16652
16911
|
function resolveProject(db, name) {
|
|
16653
|
-
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(
|
|
16912
|
+
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq24(projects.name, name)).get();
|
|
16654
16913
|
if (!row) throw notFound("project", name);
|
|
16655
16914
|
return row;
|
|
16656
16915
|
}
|
|
@@ -16758,7 +17017,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
16758
17017
|
}
|
|
16759
17018
|
requireInstanceAdministrator(request);
|
|
16760
17019
|
const project = resolveProject(opts.db, request.params.name);
|
|
16761
|
-
const row = opts.db.select().from(agentSessions).where(
|
|
17020
|
+
const row = opts.db.select().from(agentSessions).where(eq24(agentSessions.projectId, project.id)).get();
|
|
16762
17021
|
if (!row) {
|
|
16763
17022
|
return { conversationId: null, messages: [], modelProvider: null, modelId: null, updatedAt: null };
|
|
16764
17023
|
}
|
|
@@ -16798,7 +17057,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
16798
17057
|
const project = resolveProject(opts.db, request.params.name);
|
|
16799
17058
|
if (opts.sessionRegistry.isBusy(project.name)) throw agentBusy(project.name);
|
|
16800
17059
|
opts.sessionRegistry.reset(project.name);
|
|
16801
|
-
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
17060
|
+
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq24(agentSessions.projectId, project.id)).run();
|
|
16802
17061
|
return { status: "reset" };
|
|
16803
17062
|
}
|
|
16804
17063
|
);
|
|
@@ -16822,7 +17081,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
16822
17081
|
return streamViewerTurn(opts, project, viewerId, body, reply);
|
|
16823
17082
|
}
|
|
16824
17083
|
if (body.conversationId !== void 0) {
|
|
16825
|
-
const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(
|
|
17084
|
+
const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(eq24(agentSessions.projectId, project.id)).get();
|
|
16826
17085
|
if ((current?.id ?? null) !== body.conversationId) throw validationError("The active conversation changed. Reload the conversation before sending.");
|
|
16827
17086
|
}
|
|
16828
17087
|
const promptText = body.prompt;
|
|
@@ -17625,7 +17884,7 @@ function buildSnapshotSummary(companyName, queries2, providers, queryResults, au
|
|
|
17625
17884
|
competitorCounts.set(competitor, (competitorCounts.get(competitor) ?? 0) + 1);
|
|
17626
17885
|
}
|
|
17627
17886
|
}
|
|
17628
|
-
const topCompetitors = [...competitorCounts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 10).map(([name,
|
|
17887
|
+
const topCompetitors = [...competitorCounts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 10).map(([name, count2]) => ({ name, count: count2 }));
|
|
17629
17888
|
const defaultMeaning = totalComparisons > 0 ? `${companyName} was mentioned in ${mentionCount}/${totalComparisons} successful provider-query response${totalComparisons === 1 ? "" : "s"} across ${queries2.length} category queries.` : `No successful provider responses were returned across ${queries2.length} category queries.`;
|
|
17630
17889
|
const failureNote = failedComparisons > 0 ? [
|
|
17631
17890
|
`${failedComparisons} provider response${failedComparisons === 1 ? "" : "s"} failed and ${failedComparisons === 1 ? "was" : "were"} excluded from visibility totals.`
|
|
@@ -17760,20 +18019,20 @@ function clipText(value, length) {
|
|
|
17760
18019
|
}
|
|
17761
18020
|
|
|
17762
18021
|
// src/research-runner.ts
|
|
17763
|
-
import { and as
|
|
18022
|
+
import { and as and19, eq as eq25, inArray as inArray9, sql as sql9 } from "drizzle-orm";
|
|
17764
18023
|
var unfinishedResearchQueryStatuses = [ResearchQueryStatuses.queued, ResearchQueryStatuses.running];
|
|
17765
18024
|
function finalResearchRunStatus(completed, failed) {
|
|
17766
18025
|
if (failed === 0) return ResearchRunStatuses.completed;
|
|
17767
18026
|
return completed > 0 ? ResearchRunStatuses.partial : ResearchRunStatuses.failed;
|
|
17768
18027
|
}
|
|
17769
18028
|
async function executeResearchRun(db, registry, runId, projectId) {
|
|
17770
|
-
const run = db.select().from(researchRuns).where(
|
|
18029
|
+
const run = db.select().from(researchRuns).where(and19(eq25(researchRuns.id, runId), eq25(researchRuns.projectId, projectId))).get();
|
|
17771
18030
|
if (!run || run.status !== ResearchRunStatuses.queued) return;
|
|
17772
|
-
const project = db.select().from(projects).where(
|
|
18031
|
+
const project = db.select().from(projects).where(eq25(projects.id, projectId)).get();
|
|
17773
18032
|
if (!project) return;
|
|
17774
18033
|
const provider = registry.get(run.provider);
|
|
17775
18034
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
17776
|
-
const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(
|
|
18035
|
+
const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(and19(eq25(researchRuns.id, runId), eq25(researchRuns.status, ResearchRunStatuses.queued))).run();
|
|
17777
18036
|
if (claim.changes !== 1) return;
|
|
17778
18037
|
let reserved = 0;
|
|
17779
18038
|
let dispatched = 0;
|
|
@@ -17797,8 +18056,8 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
17797
18056
|
}
|
|
17798
18057
|
reserved = run.totalQueries;
|
|
17799
18058
|
reservation = { scope, period };
|
|
17800
|
-
const rows = db.select().from(researchRunQueries).where(
|
|
17801
|
-
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(
|
|
18059
|
+
const rows = db.select().from(researchRunQueries).where(eq25(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
|
|
18060
|
+
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq25(competitors.projectId, projectId)).all().map((row) => row.domain);
|
|
17802
18061
|
const domains = effectiveDomains(project);
|
|
17803
18062
|
const brands = effectiveBrandNames(project);
|
|
17804
18063
|
const config = { ...provider.config, model: run.resolvedModel };
|
|
@@ -17806,7 +18065,7 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
17806
18065
|
await mapWithConcurrency(rows, Math.max(1, provider.config.quotaPolicy.maxConcurrency), async (row) => {
|
|
17807
18066
|
try {
|
|
17808
18067
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17809
|
-
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(
|
|
18068
|
+
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(and19(eq25(researchRunQueries.id, row.id), eq25(researchRunQueries.status, ResearchQueryStatuses.queued))).run();
|
|
17810
18069
|
const raw = await gate.run(async () => {
|
|
17811
18070
|
dispatched++;
|
|
17812
18071
|
return provider.adapter.executeTrackedQuery({ query: row.queryText, canonicalDomains: domains, competitorDomains, ...run.location ? { location: run.location } : {} }, config);
|
|
@@ -17833,7 +18092,7 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
17833
18092
|
citationState: determineCitationState(normalized, domains),
|
|
17834
18093
|
rawResponse: raw.rawResponse,
|
|
17835
18094
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17836
|
-
}).where(
|
|
18095
|
+
}).where(and19(eq25(researchRunQueries.id, row.id), eq25(researchRunQueries.status, ResearchQueryStatuses.running))).run();
|
|
17837
18096
|
if (completed.changes === 1) incrementResearchProgress(db, runId, "completedQueries");
|
|
17838
18097
|
} catch (error) {
|
|
17839
18098
|
try {
|
|
@@ -17857,18 +18116,18 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
17857
18116
|
}
|
|
17858
18117
|
}
|
|
17859
18118
|
function incrementResearchProgress(db, runId, column) {
|
|
17860
|
-
db.update(researchRuns).set({ [column]: sql9`${researchRuns[column]} + 1` }).where(
|
|
18119
|
+
db.update(researchRuns).set({ [column]: sql9`${researchRuns[column]} + 1` }).where(eq25(researchRuns.id, runId)).run();
|
|
17861
18120
|
}
|
|
17862
18121
|
function markResearchQueryFailed(db, runId, queryId, error) {
|
|
17863
18122
|
const message = describeError(error);
|
|
17864
|
-
const failed = db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error: message, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
18123
|
+
const failed = db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error: message, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and19(eq25(researchRunQueries.id, queryId), inArray9(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
|
|
17865
18124
|
if (failed.changes === 1) incrementResearchProgress(db, runId, "failedQueries");
|
|
17866
18125
|
}
|
|
17867
18126
|
function markUnfinishedResearchQueriesFailed(db, runId, error) {
|
|
17868
|
-
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
18127
|
+
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and19(eq25(researchRunQueries.researchRunId, runId), inArray9(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
|
|
17869
18128
|
}
|
|
17870
18129
|
function finalizeResearchRun(db, runId, fatalError) {
|
|
17871
|
-
const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(
|
|
18130
|
+
const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq25(researchRunQueries.researchRunId, runId)).all();
|
|
17872
18131
|
const completed = rows.filter((row) => row.status === ResearchQueryStatuses.completed).length;
|
|
17873
18132
|
const failed = rows.filter((row) => row.status === ResearchQueryStatuses.failed).length;
|
|
17874
18133
|
db.update(researchRuns).set({
|
|
@@ -17877,11 +18136,11 @@ function finalizeResearchRun(db, runId, fatalError) {
|
|
|
17877
18136
|
failedQueries: failed,
|
|
17878
18137
|
error: fatalError ?? (failed === rows.length ? "Every research query failed." : null),
|
|
17879
18138
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17880
|
-
}).where(
|
|
18139
|
+
}).where(eq25(researchRuns.id, runId)).run();
|
|
17881
18140
|
}
|
|
17882
18141
|
|
|
17883
18142
|
// src/google-marketing-sync.ts
|
|
17884
|
-
import { and as
|
|
18143
|
+
import { and as and20, eq as eq26, isNull as isNull2 } from "drizzle-orm";
|
|
17885
18144
|
function safeErrorMessage(error) {
|
|
17886
18145
|
const raw = describeError(error);
|
|
17887
18146
|
return raw.replace(/(["']?authorization["']?\s*[:=]\s*["']?bearer\s+)[^\s,"'}]+/gi, "$1[redacted]").replace(/(["']?(?:developer|access|refresh|client)[_-]?(?:token|secret)["']?\s*[:=]\s*["']?)[^\s,"'}]+/gi, "$1[redacted]").replace(/[\r\n\t]+/g, " ").slice(0, 1e3);
|
|
@@ -17904,10 +18163,10 @@ function assertSnapshotContext(metadata2, expected) {
|
|
|
17904
18163
|
}
|
|
17905
18164
|
}
|
|
17906
18165
|
function requireSyncRun(db, runId, projectId, kind) {
|
|
17907
|
-
const run = db.select().from(runs).where(
|
|
17908
|
-
|
|
17909
|
-
|
|
17910
|
-
|
|
18166
|
+
const run = db.select().from(runs).where(and20(
|
|
18167
|
+
eq26(runs.id, runId),
|
|
18168
|
+
eq26(runs.projectId, projectId),
|
|
18169
|
+
eq26(runs.kind, kind)
|
|
17911
18170
|
)).get();
|
|
17912
18171
|
if (!run) throw new Error(`Run ${runId} is not a ${kind} run for this project.`);
|
|
17913
18172
|
if (run.status !== RunStatuses.queued) {
|
|
@@ -17920,19 +18179,19 @@ function markFailed(db, runId, projectId, kind, expectedStatus, error) {
|
|
|
17920
18179
|
status: RunStatuses.failed,
|
|
17921
18180
|
error: serializeRunError({ message: safeErrorMessage(error) }),
|
|
17922
18181
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17923
|
-
}).where(
|
|
17924
|
-
|
|
17925
|
-
|
|
17926
|
-
|
|
17927
|
-
|
|
18182
|
+
}).where(and20(
|
|
18183
|
+
eq26(runs.id, runId),
|
|
18184
|
+
eq26(runs.projectId, projectId),
|
|
18185
|
+
eq26(runs.kind, kind),
|
|
18186
|
+
eq26(runs.status, expectedStatus)
|
|
17928
18187
|
)).run();
|
|
17929
18188
|
}
|
|
17930
18189
|
function claimSyncRun(db, runId, projectId, kind, startedAt) {
|
|
17931
|
-
const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(
|
|
17932
|
-
|
|
17933
|
-
|
|
17934
|
-
|
|
17935
|
-
|
|
18190
|
+
const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(and20(
|
|
18191
|
+
eq26(runs.id, runId),
|
|
18192
|
+
eq26(runs.projectId, projectId),
|
|
18193
|
+
eq26(runs.kind, kind),
|
|
18194
|
+
eq26(runs.status, RunStatuses.queued)
|
|
17936
18195
|
)).run();
|
|
17937
18196
|
return claim.changes === 1;
|
|
17938
18197
|
}
|
|
@@ -17940,9 +18199,9 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
|
|
|
17940
18199
|
let claimed = false;
|
|
17941
18200
|
try {
|
|
17942
18201
|
requireSyncRun(db, runId, projectId, RunKinds["google-ads-sync"]);
|
|
17943
|
-
const project = db.select().from(projects).where(
|
|
18202
|
+
const project = db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
17944
18203
|
if (!project) throw new Error(`Project ${projectId} was not found.`);
|
|
17945
|
-
const connection = db.select().from(googleAdsConnections).where(
|
|
18204
|
+
const connection = db.select().from(googleAdsConnections).where(eq26(googleAdsConnections.projectId, projectId)).get();
|
|
17946
18205
|
if (!connection?.selectedCustomerId) {
|
|
17947
18206
|
throw new Error("Google Ads needs a selected customer before it can sync.");
|
|
17948
18207
|
}
|
|
@@ -17993,11 +18252,11 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
|
|
|
17993
18252
|
status: RunStatuses.completed,
|
|
17994
18253
|
finishedAt,
|
|
17995
18254
|
error: null
|
|
17996
|
-
}).where(
|
|
17997
|
-
|
|
17998
|
-
|
|
17999
|
-
|
|
18000
|
-
|
|
18255
|
+
}).where(and20(
|
|
18256
|
+
eq26(runs.id, runId),
|
|
18257
|
+
eq26(runs.projectId, projectId),
|
|
18258
|
+
eq26(runs.kind, RunKinds["google-ads-sync"]),
|
|
18259
|
+
eq26(runs.status, RunStatuses.running)
|
|
18001
18260
|
)).run();
|
|
18002
18261
|
if (terminalClaim.changes !== 1) {
|
|
18003
18262
|
throw new Error(`Run ${runId} was no longer running when Google Ads evidence was ready.`);
|
|
@@ -18014,12 +18273,12 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
|
|
|
18014
18273
|
lastMetricsSnapshotAt: result2.metrics?.metadata.capturedAt ?? connection.lastMetricsSnapshotAt,
|
|
18015
18274
|
lastMetricsSnapshotId: result2.metrics?.metadata.id ?? connection.lastMetricsSnapshotId,
|
|
18016
18275
|
updatedAt: finishedAt
|
|
18017
|
-
}).where(
|
|
18018
|
-
|
|
18019
|
-
|
|
18020
|
-
|
|
18021
|
-
connection.selectedLoginCustomerId === null ? isNull2(googleAdsConnections.selectedLoginCustomerId) :
|
|
18022
|
-
|
|
18276
|
+
}).where(and20(
|
|
18277
|
+
eq26(googleAdsConnections.id, connection.id),
|
|
18278
|
+
eq26(googleAdsConnections.projectId, projectId),
|
|
18279
|
+
eq26(googleAdsConnections.selectedCustomerId, connection.selectedCustomerId),
|
|
18280
|
+
connection.selectedLoginCustomerId === null ? isNull2(googleAdsConnections.selectedLoginCustomerId) : eq26(googleAdsConnections.selectedLoginCustomerId, connection.selectedLoginCustomerId),
|
|
18281
|
+
eq26(googleAdsConnections.selectionGeneration, connection.selectionGeneration)
|
|
18023
18282
|
)).run();
|
|
18024
18283
|
if (connectionClaim.changes !== 1) {
|
|
18025
18284
|
throw new Error("Google Ads selection changed while its evidence was being collected.");
|
|
@@ -18046,9 +18305,9 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
18046
18305
|
let claimed = false;
|
|
18047
18306
|
try {
|
|
18048
18307
|
requireSyncRun(db, runId, projectId, RunKinds["gtm-sync"]);
|
|
18049
|
-
const project = db.select().from(projects).where(
|
|
18308
|
+
const project = db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
18050
18309
|
if (!project) throw new Error(`Project ${projectId} was not found.`);
|
|
18051
|
-
const connection = db.select().from(gtmConnections).where(
|
|
18310
|
+
const connection = db.select().from(gtmConnections).where(eq26(gtmConnections.projectId, projectId)).get();
|
|
18052
18311
|
if (!connection?.selectedAccountId || !connection.selectedContainerId) {
|
|
18053
18312
|
throw new Error("GTM needs a selected account and container before it can sync.");
|
|
18054
18313
|
}
|
|
@@ -18083,11 +18342,11 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
18083
18342
|
status: RunStatuses.completed,
|
|
18084
18343
|
finishedAt,
|
|
18085
18344
|
error: null
|
|
18086
|
-
}).where(
|
|
18087
|
-
|
|
18088
|
-
|
|
18089
|
-
|
|
18090
|
-
|
|
18345
|
+
}).where(and20(
|
|
18346
|
+
eq26(runs.id, runId),
|
|
18347
|
+
eq26(runs.projectId, projectId),
|
|
18348
|
+
eq26(runs.kind, RunKinds["gtm-sync"]),
|
|
18349
|
+
eq26(runs.status, RunStatuses.running)
|
|
18091
18350
|
)).run();
|
|
18092
18351
|
if (terminalClaim.changes !== 1) {
|
|
18093
18352
|
throw new Error(`Run ${runId} was no longer running when GTM evidence was ready.`);
|
|
@@ -18101,13 +18360,13 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
18101
18360
|
lastSnapshotAt: snapshot.metadata.capturedAt,
|
|
18102
18361
|
lastSnapshotId: snapshot.metadata.id,
|
|
18103
18362
|
updatedAt: finishedAt
|
|
18104
|
-
}).where(
|
|
18105
|
-
|
|
18106
|
-
|
|
18107
|
-
|
|
18108
|
-
|
|
18109
|
-
connection.selectedWorkspaceId === null ? isNull2(gtmConnections.selectedWorkspaceId) :
|
|
18110
|
-
|
|
18363
|
+
}).where(and20(
|
|
18364
|
+
eq26(gtmConnections.id, connection.id),
|
|
18365
|
+
eq26(gtmConnections.projectId, projectId),
|
|
18366
|
+
eq26(gtmConnections.selectedAccountId, connection.selectedAccountId),
|
|
18367
|
+
eq26(gtmConnections.selectedContainerId, connection.selectedContainerId),
|
|
18368
|
+
connection.selectedWorkspaceId === null ? isNull2(gtmConnections.selectedWorkspaceId) : eq26(gtmConnections.selectedWorkspaceId, connection.selectedWorkspaceId),
|
|
18369
|
+
eq26(gtmConnections.selectionGeneration, connection.selectionGeneration)
|
|
18111
18370
|
)).run();
|
|
18112
18371
|
if (connectionClaim.changes !== 1) {
|
|
18113
18372
|
throw new Error("GTM selection changed while its evidence was being collected.");
|
|
@@ -18611,7 +18870,7 @@ async function createServer(opts) {
|
|
|
18611
18870
|
async (ctx) => {
|
|
18612
18871
|
if (!sessionRegistry) return;
|
|
18613
18872
|
if (!agentProactive) return;
|
|
18614
|
-
const project = opts.db.select({ name: projects.name }).from(projects).where(
|
|
18873
|
+
const project = opts.db.select({ name: projects.name }).from(projects).where(eq27(projects.id, ctx.projectId)).get();
|
|
18615
18874
|
if (!project) return;
|
|
18616
18875
|
let content;
|
|
18617
18876
|
if (ctx.kind === RunKinds["aeo-discover-probe"]) {
|
|
@@ -19068,7 +19327,7 @@ async function createServer(opts) {
|
|
|
19068
19327
|
void (async () => {
|
|
19069
19328
|
try {
|
|
19070
19329
|
const report = await schedulerClient.runDoctor({ project: projectName });
|
|
19071
|
-
const project = opts.db.select().from(projects).where(
|
|
19330
|
+
const project = opts.db.select().from(projects).where(eq27(projects.name, projectName)).get();
|
|
19072
19331
|
if (!project) {
|
|
19073
19332
|
app.log.warn({ projectName }, "doctor schedule fired for an unknown project");
|
|
19074
19333
|
return;
|
|
@@ -19096,9 +19355,9 @@ async function createServer(opts) {
|
|
|
19096
19355
|
});
|
|
19097
19356
|
if (!probed) return;
|
|
19098
19357
|
const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(
|
|
19099
|
-
|
|
19100
|
-
|
|
19101
|
-
|
|
19358
|
+
and21(
|
|
19359
|
+
eq27(ccReleaseSyncs.release, probed.release),
|
|
19360
|
+
eq27(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
|
|
19102
19361
|
)
|
|
19103
19362
|
).limit(1).get();
|
|
19104
19363
|
if (alreadySynced) {
|
|
@@ -19369,7 +19628,7 @@ async function createServer(opts) {
|
|
|
19369
19628
|
})();
|
|
19370
19629
|
if (opts.config.apiKey) {
|
|
19371
19630
|
const keyHash = hashApiKey2(opts.config.apiKey);
|
|
19372
|
-
const existing = opts.db.select().from(apiKeys).where(
|
|
19631
|
+
const existing = opts.db.select().from(apiKeys).where(eq27(apiKeys.keyHash, keyHash)).get();
|
|
19373
19632
|
if (!existing) {
|
|
19374
19633
|
const prefix = opts.config.apiKey.slice(0, 12);
|
|
19375
19634
|
opts.db.insert(apiKeys).values({
|
|
@@ -19421,7 +19680,7 @@ async function createServer(opts) {
|
|
|
19421
19680
|
};
|
|
19422
19681
|
const getDefaultApiKey = () => {
|
|
19423
19682
|
if (!opts.config.apiKey) return void 0;
|
|
19424
|
-
return opts.db.select().from(apiKeys).where(
|
|
19683
|
+
return opts.db.select().from(apiKeys).where(eq27(apiKeys.keyHash, hashApiKey2(opts.config.apiKey))).get();
|
|
19425
19684
|
};
|
|
19426
19685
|
const createPasswordSession = (reply) => {
|
|
19427
19686
|
const key = getDefaultApiKey();
|
|
@@ -19445,7 +19704,7 @@ async function createServer(opts) {
|
|
|
19445
19704
|
if (!header) return false;
|
|
19446
19705
|
const parts = header.split(" ");
|
|
19447
19706
|
if (parts.length !== 2 || parts[0] !== "Bearer") return false;
|
|
19448
|
-
const key = opts.db.select().from(apiKeys).where(
|
|
19707
|
+
const key = opts.db.select().from(apiKeys).where(eq27(apiKeys.keyHash, hashApiKey2(parts[1]))).get();
|
|
19449
19708
|
return Boolean(key && !key.revokedAt);
|
|
19450
19709
|
};
|
|
19451
19710
|
const namedAccountsInUse = () => anyUsersExist(opts.db);
|
|
@@ -19535,12 +19794,12 @@ async function createServer(opts) {
|
|
|
19535
19794
|
return reply.send({ authenticated: true });
|
|
19536
19795
|
}
|
|
19537
19796
|
if (apiKey) {
|
|
19538
|
-
const key = opts.db.select().from(apiKeys).where(
|
|
19797
|
+
const key = opts.db.select().from(apiKeys).where(eq27(apiKeys.keyHash, hashApiKey2(apiKey))).get();
|
|
19539
19798
|
if (!key || key.revokedAt) {
|
|
19540
19799
|
const err2 = authInvalid();
|
|
19541
19800
|
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
19542
19801
|
}
|
|
19543
|
-
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
19802
|
+
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq27(apiKeys.id, key.id)).run();
|
|
19544
19803
|
const sessionId = createSession(key.id);
|
|
19545
19804
|
reply.header(
|
|
19546
19805
|
"set-cookie",
|
|
@@ -19703,8 +19962,8 @@ async function createServer(opts) {
|
|
|
19703
19962
|
getAgentPin: () => describeAgentPin(opts.config),
|
|
19704
19963
|
getGoogleMarketingDoctorInput: (ctx) => {
|
|
19705
19964
|
if (!ctx.project) return null;
|
|
19706
|
-
const googleAds = opts.db.select().from(googleAdsConnections).where(
|
|
19707
|
-
const gtm = opts.db.select().from(gtmConnections).where(
|
|
19965
|
+
const googleAds = opts.db.select().from(googleAdsConnections).where(eq27(googleAdsConnections.projectId, ctx.project.id)).get();
|
|
19966
|
+
const gtm = opts.db.select().from(gtmConnections).where(eq27(gtmConnections.projectId, ctx.project.id)).get();
|
|
19708
19967
|
const googleAdsCredential = getGoogleAdsConnection(opts.config, ctx.project.id);
|
|
19709
19968
|
const gtmCredential = getGtmConnection(opts.config, ctx.project.id);
|
|
19710
19969
|
const googleAdsConnected = Boolean(googleAdsCredential?.accessToken);
|
|
@@ -19955,7 +20214,7 @@ async function createServer(opts) {
|
|
|
19955
20214
|
...inspectOpts,
|
|
19956
20215
|
config: opts.config
|
|
19957
20216
|
}).then(() => {
|
|
19958
|
-
const finished = opts.db.select({ status: runs.status }).from(runs).where(
|
|
20217
|
+
const finished = opts.db.select({ status: runs.status }).from(runs).where(eq27(runs.id, runId)).get();
|
|
19959
20218
|
if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
|
|
19960
20219
|
return maybeRefreshGscCoverage(
|
|
19961
20220
|
opts.db,
|
|
@@ -20221,7 +20480,7 @@ async function createServer(opts) {
|
|
|
20221
20480
|
}
|
|
20222
20481
|
];
|
|
20223
20482
|
},
|
|
20224
|
-
onGenerateQueries: async (providerName,
|
|
20483
|
+
onGenerateQueries: async (providerName, count2, project) => {
|
|
20225
20484
|
const provider = registry.get(providerName);
|
|
20226
20485
|
if (!provider)
|
|
20227
20486
|
throw new Error(`Provider "${providerName}" is not configured`);
|
|
@@ -20233,10 +20492,10 @@ async function createServer(opts) {
|
|
|
20233
20492
|
language: project.language,
|
|
20234
20493
|
existingQueries: project.existingQueries,
|
|
20235
20494
|
siteText,
|
|
20236
|
-
count
|
|
20495
|
+
count: count2
|
|
20237
20496
|
});
|
|
20238
20497
|
const raw = await provider.adapter.generateText(prompt, provider.config);
|
|
20239
|
-
return parseQueryResponse(raw,
|
|
20498
|
+
return parseQueryResponse(raw, count2);
|
|
20240
20499
|
},
|
|
20241
20500
|
onSnapshotRequested: async (input) => {
|
|
20242
20501
|
return snapshotService.createReport(input);
|
|
@@ -20431,7 +20690,7 @@ async function createServer(opts) {
|
|
|
20431
20690
|
},
|
|
20432
20691
|
notify: (projectId, result2) => notifier.onSiteLivenessChecked(projectId, result2)
|
|
20433
20692
|
});
|
|
20434
|
-
for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(
|
|
20693
|
+
for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq27(researchRuns.status, ResearchRunStatuses.queued)).all()) {
|
|
20435
20694
|
dispatchResearchRun(run.id, run.projectId);
|
|
20436
20695
|
}
|
|
20437
20696
|
runtimeStartupSettled = true;
|
|
@@ -20486,7 +20745,7 @@ function buildQueryGenerationPrompt(ctx) {
|
|
|
20486
20745
|
);
|
|
20487
20746
|
return lines.join("\n");
|
|
20488
20747
|
}
|
|
20489
|
-
function parseQueryResponse(raw,
|
|
20748
|
+
function parseQueryResponse(raw, count2) {
|
|
20490
20749
|
const seen = /* @__PURE__ */ new Set();
|
|
20491
20750
|
const results = [];
|
|
20492
20751
|
for (const line of raw.split("\n")) {
|
|
@@ -20502,7 +20761,7 @@ function parseQueryResponse(raw, count) {
|
|
|
20502
20761
|
if (seen.has(key)) continue;
|
|
20503
20762
|
seen.add(key);
|
|
20504
20763
|
results.push(cleaned);
|
|
20505
|
-
if (results.length >=
|
|
20764
|
+
if (results.length >= count2) break;
|
|
20506
20765
|
}
|
|
20507
20766
|
return results;
|
|
20508
20767
|
}
|