@canonry/canonry 5.16.1 → 5.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/assets/{AuditHistoryPanel-CnoGPJ5Y.js → AuditHistoryPanel-Di5c6xSa.js} +1 -1
- package/assets/assets/{BacklinksPage-DYe2CYfD.js → BacklinksPage-CZi3sZ-7.js} +1 -1
- package/assets/assets/{HistoryPage-CO-dzLAo.js → HistoryPage-BY7HiZWU.js} +1 -1
- package/assets/assets/MeasurementPropertyPage-D3AdBHpI.js +1 -0
- package/assets/assets/ProjectPage-NyUl1dkF.js +8 -0
- package/assets/assets/{RunRow-CzcaoK_3.js → RunRow-cRjiHQbt.js} +1 -1
- package/assets/assets/{RunsPage-DQWPHDoq.js → RunsPage-DZP_kqJs.js} +1 -1
- package/assets/assets/{SettingsPage-DMtQLrWP.js → SettingsPage-BqJXYCDc.js} +1 -1
- package/assets/assets/{SiteHealthSection-2ljW02xK.js → SiteHealthSection-Brr0C56X.js} +3 -3
- package/assets/assets/{TrafficPage-NFu0LEdk.js → TrafficPage-k5NPSDur.js} +2 -2
- package/assets/assets/{TrafficSourceDetailPage-DbBIzmbL.js → TrafficSourceDetailPage-BKNzWnxA.js} +1 -1
- package/assets/assets/{extract-error-message-pMGpUsIh.js → extract-error-message-Cnvelll0.js} +1 -1
- package/assets/assets/index-Cd_1GknE.js +86 -0
- package/assets/assets/{react-sigma_core.esm.min-CadBie52.js → react-sigma_core.esm.min-CthGjQKl.js} +1 -1
- package/assets/assets/v2-overview-adapter-q2o2MzmG.js +1 -0
- package/assets/index.html +1 -1
- package/dist/{chunk-L36TC6LB.js → chunk-77VHT3JD.js} +29 -3
- package/dist/{chunk-CDQDPUY4.js → chunk-HAABFHXB.js} +2 -2
- package/dist/{chunk-5QK5KHA7.js → chunk-HIFBIDOW.js} +485 -160
- package/dist/{chunk-ZGZSROSR.js → chunk-TMTO2JHU.js} +9 -2
- package/dist/{chunk-JO5HHEJV.js → chunk-X3GFCKUJ.js} +2 -2
- package/dist/{chunk-3YAPVVFN.js → chunk-Y5IDGH5B.js} +52 -17
- package/dist/cli.js +18 -8
- package/dist/{demo-server-QLQUVMKJ.js → demo-server-JBGYZKAI.js} +3 -3
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -5
- package/dist/{intelligence-service-EAUKUVVM.js → intelligence-service-EORTVFDF.js} +2 -2
- package/dist/mcp.js +3 -3
- package/package.json +11 -11
- package/assets/assets/MeasurementPropertyPage-smsZFdd6.js +0 -1
- package/assets/assets/ProjectPage-jL3jB2TR.js +0 -8
- package/assets/assets/index-D9NReRHX.js +0 -86
- package/assets/assets/v2-overview-adapter-Oe8hHMAO.js +0 -1
|
@@ -17,12 +17,12 @@ import {
|
|
|
17
17
|
resolveOperatorApiKeyIds,
|
|
18
18
|
runWithUsageTags,
|
|
19
19
|
saveConfigPatch
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-TMTO2JHU.js";
|
|
21
21
|
import {
|
|
22
22
|
deleteSiteCrawlGraphLayout,
|
|
23
23
|
persistSiteCrawlGraphLayout,
|
|
24
24
|
prepareSiteCrawlGraphLayout
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-HAABFHXB.js";
|
|
26
26
|
import {
|
|
27
27
|
PACKAGE_VERSION
|
|
28
28
|
} from "./chunk-AOJU5LKX.js";
|
|
@@ -184,7 +184,7 @@ import {
|
|
|
184
184
|
siteLivenessState,
|
|
185
185
|
toAlertView,
|
|
186
186
|
usageCounters
|
|
187
|
-
} from "./chunk-
|
|
187
|
+
} from "./chunk-Y5IDGH5B.js";
|
|
188
188
|
import {
|
|
189
189
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
190
190
|
AGENT_NONE,
|
|
@@ -194,6 +194,7 @@ import {
|
|
|
194
194
|
AdsAdGroupBillingEventTypes,
|
|
195
195
|
AdsCampaignBiddingTypes,
|
|
196
196
|
AgentProviderIds,
|
|
197
|
+
AppError,
|
|
197
198
|
BacklinkSources,
|
|
198
199
|
CANONRY_NPM_PACKAGE_URL,
|
|
199
200
|
CITED_URL_CAPTURE_VERSION,
|
|
@@ -232,6 +233,7 @@ import {
|
|
|
232
233
|
TrafficEventKinds,
|
|
233
234
|
TrafficEvidenceKinds,
|
|
234
235
|
TrafficSourceTypes,
|
|
236
|
+
UserRoles,
|
|
235
237
|
VERTEX_AI_SEARCH_PROXY_DOMAIN,
|
|
236
238
|
actionConfidenceLabel,
|
|
237
239
|
adsAccountDtoSchema,
|
|
@@ -345,7 +347,7 @@ import {
|
|
|
345
347
|
visibilityReportRequestSchema,
|
|
346
348
|
winnabilityClassLabel,
|
|
347
349
|
withRetry
|
|
348
|
-
} from "./chunk-
|
|
350
|
+
} from "./chunk-77VHT3JD.js";
|
|
349
351
|
|
|
350
352
|
// src/runtime-context.ts
|
|
351
353
|
function cliRuntimeContext(env = process.env, stdio = process) {
|
|
@@ -3560,11 +3562,11 @@ function createGoogleMarketingRuntime(options) {
|
|
|
3560
3562
|
|
|
3561
3563
|
// src/server.ts
|
|
3562
3564
|
import { createRequire as createRequire3 } from "module";
|
|
3563
|
-
import
|
|
3565
|
+
import crypto27 from "crypto";
|
|
3564
3566
|
import fs8 from "fs";
|
|
3565
3567
|
import path8 from "path";
|
|
3566
3568
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3567
|
-
import { and as
|
|
3569
|
+
import { and as and20, eq as eq26 } from "drizzle-orm";
|
|
3568
3570
|
import Fastify from "fastify";
|
|
3569
3571
|
import os4 from "os";
|
|
3570
3572
|
|
|
@@ -6063,6 +6065,11 @@ function resolveAgentProactiveEnabled(env, config) {
|
|
|
6063
6065
|
}
|
|
6064
6066
|
return config.agent?.mode !== "prompt-only";
|
|
6065
6067
|
}
|
|
6068
|
+
function resolveAgentAllowViewers(env, config) {
|
|
6069
|
+
const raw = env.CANONRY_AGENT_ALLOW_VIEWERS?.trim();
|
|
6070
|
+
if (raw) return raw === "1" || raw.toLowerCase() === "true";
|
|
6071
|
+
return config.agent?.allowViewers === true;
|
|
6072
|
+
}
|
|
6066
6073
|
|
|
6067
6074
|
// src/places-config.ts
|
|
6068
6075
|
var DEFAULT_TIER = "atmosphere";
|
|
@@ -6491,7 +6498,7 @@ function nonBlank(value) {
|
|
|
6491
6498
|
return trimmed ? trimmed : void 0;
|
|
6492
6499
|
}
|
|
6493
6500
|
function resolveBuildCommit(env = process.env) {
|
|
6494
|
-
const embedded = true ? "
|
|
6501
|
+
const embedded = true ? "508d40ec00ff12e4604267c10940212dd3b750b9" : void 0;
|
|
6495
6502
|
return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
|
|
6496
6503
|
}
|
|
6497
6504
|
function resolveInstanceIdentity(env = process.env) {
|
|
@@ -12067,7 +12074,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
12067
12074
|
return result2;
|
|
12068
12075
|
}
|
|
12069
12076
|
async function backfillInsightsCommand(project, opts) {
|
|
12070
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
12077
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-EORTVFDF.js");
|
|
12071
12078
|
const config = loadConfig();
|
|
12072
12079
|
const db = createClient(config.database);
|
|
12073
12080
|
migrate(db);
|
|
@@ -15130,7 +15137,7 @@ function ensureBuiltinsRegistered() {
|
|
|
15130
15137
|
builtinsRegistered = true;
|
|
15131
15138
|
}
|
|
15132
15139
|
}
|
|
15133
|
-
function loadAeroSystemPrompt(pkgDir) {
|
|
15140
|
+
function loadAeroSystemPrompt(pkgDir, opts = {}) {
|
|
15134
15141
|
const skillDir = resolveAeroSkillDir(pkgDir);
|
|
15135
15142
|
const skillBody = fs7.readFileSync(path7.join(skillDir, "SKILL.md"), "utf-8");
|
|
15136
15143
|
const soulPath = path7.join(skillDir, "soul.md");
|
|
@@ -15139,7 +15146,7 @@ function loadAeroSystemPrompt(pkgDir) {
|
|
|
15139
15146
|
---
|
|
15140
15147
|
|
|
15141
15148
|
${skillBody}` : skillBody;
|
|
15142
|
-
return appendSystemPromptExtras(base + AERO_RUNTIME_PROMPT);
|
|
15149
|
+
return opts.extras === false ? base + AERO_RUNTIME_PROMPT : appendSystemPromptExtras(base + AERO_RUNTIME_PROMPT);
|
|
15143
15150
|
}
|
|
15144
15151
|
function appendSystemPromptExtras(base, env = process.env) {
|
|
15145
15152
|
const inline = env.AERO_SYSTEM_PROMPT_APPEND?.trim();
|
|
@@ -16200,16 +16207,223 @@ ${lines.join("\n")}
|
|
|
16200
16207
|
}
|
|
16201
16208
|
};
|
|
16202
16209
|
|
|
16203
|
-
// src/
|
|
16210
|
+
// src/agent/viewer-sessions.ts
|
|
16204
16211
|
import crypto25 from "crypto";
|
|
16205
|
-
import { eq as eq21 } from "drizzle-orm";
|
|
16212
|
+
import { and as and17, eq as eq21, isNotNull, like as like2 } from "drizzle-orm";
|
|
16213
|
+
var AERO_VIEWER_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
|
|
16214
|
+
CanonryMcpToolNames.canonry_ads_account,
|
|
16215
|
+
CanonryMcpToolNames.canonry_ads_geo_search,
|
|
16216
|
+
CanonryMcpToolNames.canonry_ads_live_delivery,
|
|
16217
|
+
CanonryMcpToolNames.canonry_ads_conversion_pixels,
|
|
16218
|
+
CanonryMcpToolNames.canonry_ads_conversion_event_settings,
|
|
16219
|
+
CanonryMcpToolNames.canonry_google_ads_customers,
|
|
16220
|
+
CanonryMcpToolNames.canonry_gtm_accounts,
|
|
16221
|
+
CanonryMcpToolNames.canonry_gtm_containers,
|
|
16222
|
+
CanonryMcpToolNames.canonry_gtm_workspaces,
|
|
16223
|
+
// These can list every account the install's Google or provider login can
|
|
16224
|
+
// see, other clients included, or run live checks. Not a viewer's to read.
|
|
16225
|
+
CanonryMcpToolNames.canonry_settings_get,
|
|
16226
|
+
CanonryMcpToolNames.canonry_doctor,
|
|
16227
|
+
CanonryMcpToolNames.canonry_ga_properties,
|
|
16228
|
+
CanonryMcpToolNames.canonry_gbp_accounts,
|
|
16229
|
+
// Marked read, but embeds the harvested queries with the operator's Gemini key.
|
|
16230
|
+
CanonryMcpToolNames.canonry_discover_harvest,
|
|
16231
|
+
// Live Search Console call on the operator's Google login, which can also
|
|
16232
|
+
// refresh and store its token.
|
|
16233
|
+
CanonryMcpToolNames.canonry_gsc_sitemaps,
|
|
16234
|
+
// Live Common Crawl probe; free, but an outbound call a viewer has no need to make.
|
|
16235
|
+
CanonryMcpToolNames.canonry_backlinks_latest_release,
|
|
16236
|
+
// The whole install's audit log, not this project's.
|
|
16237
|
+
CanonryMcpToolNames.canonry_history_global
|
|
16238
|
+
]);
|
|
16239
|
+
var OPERATOR_ONLY_TOOL_NAMES = new Set(
|
|
16240
|
+
canonryMcpTools.filter((tool) => tool.tier === "agent" || tool.requiresOperator).map((tool) => tool.name)
|
|
16241
|
+
);
|
|
16242
|
+
var VIEWER_AERO_TURN_LIMITS = { maxToolCalls: 12, timeoutMs: 12e4 };
|
|
16243
|
+
var VIEWER_AERO_MAX_PROMPT_CHARS = 4e3;
|
|
16244
|
+
var VIEWER_AERO_DAILY_TURN_LIMIT = 50;
|
|
16245
|
+
function viewerAeroDailyLimitExceeded(limit) {
|
|
16246
|
+
return new AppError(
|
|
16247
|
+
"QUOTA_EXCEEDED",
|
|
16248
|
+
`You have used today's ${limit} Aero questions for this project. Try again tomorrow or ask an administrator.`,
|
|
16249
|
+
429,
|
|
16250
|
+
{ metric: "aero.viewer.turns", limit }
|
|
16251
|
+
);
|
|
16252
|
+
}
|
|
16253
|
+
var VIEWER_AERO_KEY_PREFIX = "aero-viewer:";
|
|
16254
|
+
var VIEWER_AERO_PROMPT = "\n\nThis conversation is with a view-only account. You can read this project's data but cannot change anything, start or schedule sweeps, or spend on live provider reads. If asked to, say an administrator has to do it. You cannot see the operator's own Aero conversations or notes.";
|
|
16255
|
+
var MAX_VIEWER_MESSAGES = 40;
|
|
16256
|
+
function trimViewerTranscript(messages, max = MAX_VIEWER_MESSAGES) {
|
|
16257
|
+
if (messages.length <= max) return messages;
|
|
16258
|
+
for (let i = messages.length - max; i < messages.length; i++) {
|
|
16259
|
+
if (messages[i].role === "user") return messages.slice(i);
|
|
16260
|
+
}
|
|
16261
|
+
const lastUser = messages.map((message) => message.role).lastIndexOf("user");
|
|
16262
|
+
return lastUser > 0 ? messages.slice(lastUser) : messages;
|
|
16263
|
+
}
|
|
16264
|
+
var VIEWER_IDLE_MS = 12 * 60 * 60 * 1e3;
|
|
16265
|
+
function mintViewerAeroKey(db, userId, now = /* @__PURE__ */ new Date()) {
|
|
16266
|
+
const raw = `cnry_${crypto25.randomBytes(24).toString("hex")}`;
|
|
16267
|
+
const id = crypto25.randomUUID();
|
|
16268
|
+
db.insert(apiKeys).values({
|
|
16269
|
+
id,
|
|
16270
|
+
name: `${VIEWER_AERO_KEY_PREFIX}${userId}`,
|
|
16271
|
+
keyHash: hashApiKey(raw),
|
|
16272
|
+
keyPrefix: raw.slice(0, 9),
|
|
16273
|
+
scopes: ["read"],
|
|
16274
|
+
delegatedUserId: userId,
|
|
16275
|
+
createdAt: now.toISOString()
|
|
16276
|
+
}).run();
|
|
16277
|
+
return { id, raw };
|
|
16278
|
+
}
|
|
16279
|
+
function buildViewerAeroTools(client, projectName, managedSweeps = false) {
|
|
16280
|
+
const state = buildAeroStateTools({ client, projectName }, {
|
|
16281
|
+
scope: AeroToolScopes.readOnly,
|
|
16282
|
+
managedSweeps
|
|
16283
|
+
}).filter((tool) => !AERO_VIEWER_EXCLUDED_MCP_TOOLS.has(tool.name) && !OPERATOR_ONLY_TOOL_NAMES.has(tool.name));
|
|
16284
|
+
return [...state, ...buildSkillDocTools()];
|
|
16285
|
+
}
|
|
16286
|
+
var ViewerAeroSessions = class {
|
|
16287
|
+
constructor(opts) {
|
|
16288
|
+
this.opts = opts;
|
|
16289
|
+
this.now = opts.now ?? Date.now;
|
|
16290
|
+
this.revokeOrphanedKeys();
|
|
16291
|
+
}
|
|
16292
|
+
opts;
|
|
16293
|
+
sessions = /* @__PURE__ */ new Map();
|
|
16294
|
+
acquisitions = /* @__PURE__ */ new Set();
|
|
16295
|
+
/** Turns per viewer and project today, keyed with the UTC date. In memory: a restart forgives. */
|
|
16296
|
+
turnsToday = /* @__PURE__ */ new Map();
|
|
16297
|
+
now;
|
|
16298
|
+
key(projectName, userId) {
|
|
16299
|
+
return `${projectName}\0${userId}`;
|
|
16300
|
+
}
|
|
16301
|
+
/** Without tool `details`, which can be large and the bar never renders; the model's text copy stays. */
|
|
16302
|
+
transcript(projectName, userId) {
|
|
16303
|
+
this.evictIdle();
|
|
16304
|
+
const messages = this.sessions.get(this.key(projectName, userId))?.agent.state.messages ?? [];
|
|
16305
|
+
return withoutPersistedToolDetails(messages);
|
|
16306
|
+
}
|
|
16307
|
+
updatedAt(projectName, userId) {
|
|
16308
|
+
return this.sessions.get(this.key(projectName, userId))?.updatedAt ?? null;
|
|
16309
|
+
}
|
|
16310
|
+
isBusy(projectName, userId) {
|
|
16311
|
+
const key = this.key(projectName, userId);
|
|
16312
|
+
return this.acquisitions.has(key) || this.sessions.get(key)?.agent.state.isStreaming === true;
|
|
16313
|
+
}
|
|
16314
|
+
reset(projectName, userId) {
|
|
16315
|
+
const key = this.key(projectName, userId);
|
|
16316
|
+
if (this.isBusy(projectName, userId)) throw agentBusy(projectName);
|
|
16317
|
+
this.sessions.delete(key);
|
|
16318
|
+
}
|
|
16319
|
+
async acquireForTurn(project, userId, preferences = {}) {
|
|
16320
|
+
preferences.signal?.throwIfAborted();
|
|
16321
|
+
this.evictIdle();
|
|
16322
|
+
const key = this.key(project.name, userId);
|
|
16323
|
+
if (this.isBusy(project.name, userId)) throw agentBusy(project.name);
|
|
16324
|
+
const today = new Date(this.now()).toISOString().slice(0, 10);
|
|
16325
|
+
const used = this.turnsToday.get(key);
|
|
16326
|
+
const count = used?.date === today ? used.count : 0;
|
|
16327
|
+
if (count >= VIEWER_AERO_DAILY_TURN_LIMIT) throw viewerAeroDailyLimitExceeded(VIEWER_AERO_DAILY_TURN_LIMIT);
|
|
16328
|
+
const minted = mintViewerAeroKey(this.opts.db, userId, new Date(this.now()));
|
|
16329
|
+
this.acquisitions.add(key);
|
|
16330
|
+
let handedOff = false;
|
|
16331
|
+
try {
|
|
16332
|
+
const client = new ApiClient(this.opts.selfApiUrl, minted.raw, { skipProbe: true, surface: "aero" });
|
|
16333
|
+
const agent = this.sessions.get(key)?.agent ?? this.createAgent(project, client);
|
|
16334
|
+
agent.state.messages = trimViewerTranscript(agent.state.messages);
|
|
16335
|
+
const view = { client, projectName: project.name, basePath: this.opts.config.basePath, context: preferences.context };
|
|
16336
|
+
const evidence = preferences.context ? await readAeroViewEvidence(view) : void 0;
|
|
16337
|
+
preferences.signal?.throwIfAborted();
|
|
16338
|
+
agent.state.systemPrompt = loadAeroSystemPrompt(void 0, { extras: false }) + VIEWER_AERO_PROMPT + aeroViewPrompt(preferences.context);
|
|
16339
|
+
configureAeroRuntime(agent, [...buildViewerAeroTools(client, project.name, this.opts.managedSweeps), buildAeroViewTool(view, evidence)], VIEWER_AERO_TURN_LIMITS, true);
|
|
16340
|
+
this.sessions.set(key, { agent, lastUsedAt: this.now(), updatedAt: this.sessions.get(key)?.updatedAt ?? null });
|
|
16341
|
+
this.turnsToday.set(key, { date: today, count: count + 1 });
|
|
16342
|
+
handedOff = true;
|
|
16343
|
+
let released = false;
|
|
16344
|
+
return {
|
|
16345
|
+
agent,
|
|
16346
|
+
release: () => {
|
|
16347
|
+
if (released) return;
|
|
16348
|
+
released = true;
|
|
16349
|
+
this.acquisitions.delete(key);
|
|
16350
|
+
const session = this.sessions.get(key);
|
|
16351
|
+
if (session) {
|
|
16352
|
+
session.lastUsedAt = this.now();
|
|
16353
|
+
const previous = session.updatedAt ? Date.parse(session.updatedAt) : 0;
|
|
16354
|
+
session.updatedAt = new Date(Math.max(this.now(), previous + 1)).toISOString();
|
|
16355
|
+
}
|
|
16356
|
+
this.removeKey(minted.id);
|
|
16357
|
+
}
|
|
16358
|
+
};
|
|
16359
|
+
} finally {
|
|
16360
|
+
if (!handedOff) {
|
|
16361
|
+
this.acquisitions.delete(key);
|
|
16362
|
+
this.removeKey(minted.id);
|
|
16363
|
+
}
|
|
16364
|
+
}
|
|
16365
|
+
}
|
|
16366
|
+
createAgent(project, client) {
|
|
16367
|
+
const { provider, modelId } = resolveSessionProviderAndModel(this.opts.config);
|
|
16368
|
+
return createAeroSession({
|
|
16369
|
+
projectName: project.name,
|
|
16370
|
+
client,
|
|
16371
|
+
config: this.opts.config,
|
|
16372
|
+
provider,
|
|
16373
|
+
modelId,
|
|
16374
|
+
systemPromptOverride: loadAeroSystemPrompt(void 0, { extras: false }) + VIEWER_AERO_PROMPT,
|
|
16375
|
+
toolScope: AeroToolScopes.readOnly,
|
|
16376
|
+
managedSweeps: this.opts.managedSweeps,
|
|
16377
|
+
db: this.opts.db,
|
|
16378
|
+
projectId: project.id
|
|
16379
|
+
});
|
|
16380
|
+
}
|
|
16381
|
+
/**
|
|
16382
|
+
* Delete the turn's key rather than revoke it: the key list shows revoked
|
|
16383
|
+
* keys too, and one per turn would bury the operator's own. Revoke only if
|
|
16384
|
+
* the delete fails, so the key is dead either way.
|
|
16385
|
+
*/
|
|
16386
|
+
removeKey(id) {
|
|
16387
|
+
try {
|
|
16388
|
+
this.opts.db.delete(apiKeys).where(eq21(apiKeys.id, id)).run();
|
|
16389
|
+
} catch {
|
|
16390
|
+
try {
|
|
16391
|
+
this.opts.db.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(eq21(apiKeys.id, id)).run();
|
|
16392
|
+
} catch {
|
|
16393
|
+
}
|
|
16394
|
+
}
|
|
16395
|
+
}
|
|
16396
|
+
/**
|
|
16397
|
+
* A key outlives its turn only if the process died mid-turn. Remove those on
|
|
16398
|
+
* start. Only delegated keys match: the key API never sets a delegate, so an
|
|
16399
|
+
* operator key that merely shares the name prefix is left alone.
|
|
16400
|
+
*/
|
|
16401
|
+
revokeOrphanedKeys() {
|
|
16402
|
+
try {
|
|
16403
|
+
this.opts.db.delete(apiKeys).where(and17(like2(apiKeys.name, `${VIEWER_AERO_KEY_PREFIX}%`), isNotNull(apiKeys.delegatedUserId))).run();
|
|
16404
|
+
} catch {
|
|
16405
|
+
}
|
|
16406
|
+
}
|
|
16407
|
+
evictIdle() {
|
|
16408
|
+
const cutoff = this.now() - VIEWER_IDLE_MS;
|
|
16409
|
+
for (const [key, session] of this.sessions) {
|
|
16410
|
+
if (session.lastUsedAt < cutoff && !this.acquisitions.has(key) && !session.agent.state.isStreaming) {
|
|
16411
|
+
this.sessions.delete(key);
|
|
16412
|
+
}
|
|
16413
|
+
}
|
|
16414
|
+
}
|
|
16415
|
+
};
|
|
16416
|
+
|
|
16417
|
+
// src/mcp-http.ts
|
|
16418
|
+
import crypto26 from "crypto";
|
|
16419
|
+
import { eq as eq22 } from "drizzle-orm";
|
|
16206
16420
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
16207
16421
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
16208
16422
|
var SESSION_IDLE_MS = 10 * 60 * 1e3;
|
|
16209
16423
|
var SWEEP_INTERVAL_MS = 60 * 1e3;
|
|
16210
16424
|
function mintSessionKey(db, scopes, userId) {
|
|
16211
|
-
const raw = `cnry_${
|
|
16212
|
-
const id =
|
|
16425
|
+
const raw = `cnry_${crypto26.randomBytes(24).toString("hex")}`;
|
|
16426
|
+
const id = crypto26.randomUUID();
|
|
16213
16427
|
db.insert(apiKeys).values({
|
|
16214
16428
|
id,
|
|
16215
16429
|
name: `mcp-session:${userId}`,
|
|
@@ -16223,7 +16437,7 @@ function mintSessionKey(db, scopes, userId) {
|
|
|
16223
16437
|
}
|
|
16224
16438
|
function revokeSessionKey(db, id) {
|
|
16225
16439
|
try {
|
|
16226
|
-
db.update(apiKeys).set({ revokedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
16440
|
+
db.update(apiKeys).set({ revokedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(apiKeys.id, id)).run();
|
|
16227
16441
|
} catch {
|
|
16228
16442
|
}
|
|
16229
16443
|
}
|
|
@@ -16293,7 +16507,7 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
16293
16507
|
clientName: `canonry-mcp/${PACKAGE_VERSION}`,
|
|
16294
16508
|
surface: "mcp-http",
|
|
16295
16509
|
// Correlation only: this is never accepted as caller identity or authority.
|
|
16296
|
-
actorSession:
|
|
16510
|
+
actorSession: crypto26.randomUUID()
|
|
16297
16511
|
});
|
|
16298
16512
|
let server;
|
|
16299
16513
|
try {
|
|
@@ -16310,7 +16524,7 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
16310
16524
|
throw error;
|
|
16311
16525
|
}
|
|
16312
16526
|
const transport = new StreamableHTTPServerTransport({
|
|
16313
|
-
sessionIdGenerator: () =>
|
|
16527
|
+
sessionIdGenerator: () => crypto26.randomUUID(),
|
|
16314
16528
|
onsessioninitialized: (id) => {
|
|
16315
16529
|
sessions.set(id, { transport, close, authorizationId, segmentId: segment.id, lastSeenAt: now() });
|
|
16316
16530
|
}
|
|
@@ -16406,20 +16620,145 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
16406
16620
|
}
|
|
16407
16621
|
|
|
16408
16622
|
// src/agent/agent-routes.ts
|
|
16409
|
-
import { eq as
|
|
16623
|
+
import { eq as eq23 } from "drizzle-orm";
|
|
16624
|
+
function viewerAeroCaller(request, opts) {
|
|
16625
|
+
if (!opts.viewerSessions) return null;
|
|
16626
|
+
const principal = request.principal;
|
|
16627
|
+
return principal?.kind === "user" && principal.role === UserRoles.viewer ? principal.id : null;
|
|
16628
|
+
}
|
|
16629
|
+
var VIEWER_HIDDEN_ASSISTANT_KEYS = ["api", "provider", "model", "responseId", "usage"];
|
|
16630
|
+
var VIEWER_ERROR_MESSAGE = "Aero could not finish this answer. Try again, or ask an administrator if it keeps happening.";
|
|
16631
|
+
function viewerRedact(_key, value) {
|
|
16632
|
+
if (!value || typeof value !== "object" || value.role !== "assistant") return value;
|
|
16633
|
+
const copy = { ...value };
|
|
16634
|
+
for (const key of VIEWER_HIDDEN_ASSISTANT_KEYS) delete copy[key];
|
|
16635
|
+
if (typeof copy.errorMessage === "string" && copy.errorMessage) copy.errorMessage = VIEWER_ERROR_MESSAGE;
|
|
16636
|
+
return copy;
|
|
16637
|
+
}
|
|
16638
|
+
function redactForViewer(value) {
|
|
16639
|
+
return JSON.parse(JSON.stringify(value, viewerRedact));
|
|
16640
|
+
}
|
|
16641
|
+
function viewerTranscript(opts, projectName, userId) {
|
|
16642
|
+
const sessions = opts.viewerSessions;
|
|
16643
|
+
return {
|
|
16644
|
+
conversationId: null,
|
|
16645
|
+
messages: redactForViewer(sessions.transcript(projectName, userId)),
|
|
16646
|
+
isStreaming: sessions.isBusy(projectName, userId),
|
|
16647
|
+
modelProvider: null,
|
|
16648
|
+
modelId: null,
|
|
16649
|
+
updatedAt: sessions.updatedAt(projectName, userId)
|
|
16650
|
+
};
|
|
16651
|
+
}
|
|
16410
16652
|
function resolveProject(db, name) {
|
|
16411
|
-
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(
|
|
16653
|
+
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq23(projects.name, name)).get();
|
|
16412
16654
|
if (!row) throw notFound("project", name);
|
|
16413
16655
|
return row;
|
|
16414
16656
|
}
|
|
16657
|
+
function promptMessage(prompt, context) {
|
|
16658
|
+
return {
|
|
16659
|
+
role: "user",
|
|
16660
|
+
content: prompt,
|
|
16661
|
+
timestamp: Date.now(),
|
|
16662
|
+
...context ? { aeroContext: context } : {}
|
|
16663
|
+
};
|
|
16664
|
+
}
|
|
16665
|
+
async function streamAgentTurn(reply, agent, turn) {
|
|
16666
|
+
reply.raw.writeHead(200, {
|
|
16667
|
+
"Content-Type": "text/event-stream",
|
|
16668
|
+
"Cache-Control": "no-cache",
|
|
16669
|
+
"Connection": "keep-alive",
|
|
16670
|
+
"X-Accel-Buffering": "no"
|
|
16671
|
+
});
|
|
16672
|
+
const write = (payload) => {
|
|
16673
|
+
if (reply.raw.writableEnded) return;
|
|
16674
|
+
try {
|
|
16675
|
+
const frame = turn.viewer && payload.type === "error" ? { type: "error", message: VIEWER_ERROR_MESSAGE } : payload;
|
|
16676
|
+
reply.raw.write(`data: ${JSON.stringify(frame, turn.viewer ? viewerRedact : void 0)}
|
|
16677
|
+
|
|
16678
|
+
`);
|
|
16679
|
+
} catch {
|
|
16680
|
+
}
|
|
16681
|
+
};
|
|
16682
|
+
write({ type: "stream_open" });
|
|
16683
|
+
const unsubscribe = agent.subscribe((event) => {
|
|
16684
|
+
if (event.type === "tool_execution_start") {
|
|
16685
|
+
const labelled = { ...event, label: agent.state.tools.find((tool) => tool.name === event.toolName)?.label };
|
|
16686
|
+
write(labelled);
|
|
16687
|
+
} else write(event);
|
|
16688
|
+
});
|
|
16689
|
+
let failed = false;
|
|
16690
|
+
try {
|
|
16691
|
+
await agent.prompt(turn.batch);
|
|
16692
|
+
await agent.waitForIdle();
|
|
16693
|
+
} catch (err) {
|
|
16694
|
+
failed = true;
|
|
16695
|
+
write({ type: "error", message: describeError(err) });
|
|
16696
|
+
} finally {
|
|
16697
|
+
try {
|
|
16698
|
+
turn.save();
|
|
16699
|
+
} catch (err) {
|
|
16700
|
+
failed = true;
|
|
16701
|
+
write({ type: "error", message: describeError(err) });
|
|
16702
|
+
}
|
|
16703
|
+
const status = aeroTurnStatus(agent);
|
|
16704
|
+
write({ type: "aero_turn_status", status: status && failed ? { ...status, reason: "error" } : status });
|
|
16705
|
+
unsubscribe();
|
|
16706
|
+
turn.detach();
|
|
16707
|
+
write({ type: "stream_close" });
|
|
16708
|
+
if (!reply.raw.writableEnded) {
|
|
16709
|
+
reply.raw.end();
|
|
16710
|
+
}
|
|
16711
|
+
}
|
|
16712
|
+
}
|
|
16713
|
+
async function streamViewerTurn(opts, project, viewerId, body, reply) {
|
|
16714
|
+
const disconnected = new AbortController();
|
|
16715
|
+
const live = {};
|
|
16716
|
+
const onClose = () => {
|
|
16717
|
+
if (!reply.raw.writableEnded) {
|
|
16718
|
+
disconnected.abort();
|
|
16719
|
+
live.agent?.abort();
|
|
16720
|
+
}
|
|
16721
|
+
};
|
|
16722
|
+
reply.raw.once("close", onClose);
|
|
16723
|
+
let turn;
|
|
16724
|
+
try {
|
|
16725
|
+
turn = await opts.viewerSessions.acquireForTurn(project, viewerId, { context: body.context, signal: disconnected.signal });
|
|
16726
|
+
} catch (err) {
|
|
16727
|
+
reply.raw.off("close", onClose);
|
|
16728
|
+
if (disconnected.signal.aborted) return reply;
|
|
16729
|
+
throw err;
|
|
16730
|
+
}
|
|
16731
|
+
live.agent = turn.agent;
|
|
16732
|
+
if (disconnected.signal.aborted) {
|
|
16733
|
+
turn.release();
|
|
16734
|
+
return reply;
|
|
16735
|
+
}
|
|
16736
|
+
try {
|
|
16737
|
+
await streamAgentTurn(reply, turn.agent, {
|
|
16738
|
+
batch: promptMessage(body.prompt, body.context),
|
|
16739
|
+
save: () => {
|
|
16740
|
+
},
|
|
16741
|
+
detach: () => reply.raw.off("close", onClose),
|
|
16742
|
+
viewer: true
|
|
16743
|
+
});
|
|
16744
|
+
} finally {
|
|
16745
|
+
turn.release();
|
|
16746
|
+
}
|
|
16747
|
+
return reply;
|
|
16748
|
+
}
|
|
16415
16749
|
function registerAgentRoutes(app, opts) {
|
|
16416
16750
|
registerAgentConversationRoutes(app, { db: opts.db, runtime: opts.sessionRegistry });
|
|
16417
16751
|
app.get(
|
|
16418
16752
|
"/projects/:name/agent/transcript",
|
|
16419
16753
|
async (request) => {
|
|
16754
|
+
const viewerId = viewerAeroCaller(request, opts);
|
|
16755
|
+
if (viewerId) {
|
|
16756
|
+
const project2 = resolveProject(opts.db, request.params.name);
|
|
16757
|
+
return viewerTranscript(opts, project2.name, viewerId);
|
|
16758
|
+
}
|
|
16420
16759
|
requireInstanceAdministrator(request);
|
|
16421
16760
|
const project = resolveProject(opts.db, request.params.name);
|
|
16422
|
-
const row = opts.db.select().from(agentSessions).where(
|
|
16761
|
+
const row = opts.db.select().from(agentSessions).where(eq23(agentSessions.projectId, project.id)).get();
|
|
16423
16762
|
if (!row) {
|
|
16424
16763
|
return { conversationId: null, messages: [], modelProvider: null, modelId: null, updatedAt: null };
|
|
16425
16764
|
}
|
|
@@ -16444,23 +16783,46 @@ function registerAgentRoutes(app, opts) {
|
|
|
16444
16783
|
);
|
|
16445
16784
|
app.delete(
|
|
16446
16785
|
"/projects/:name/agent/transcript",
|
|
16786
|
+
// For a viewer this writes nothing: it drops their own in-memory
|
|
16787
|
+
// conversation. Everyone else still meets the administrator gate below
|
|
16788
|
+
// before anything is written, so the grant widens nothing for them.
|
|
16789
|
+
{ config: { readSemantic: true } },
|
|
16447
16790
|
async (request) => {
|
|
16791
|
+
const viewerId = viewerAeroCaller(request, opts);
|
|
16792
|
+
if (viewerId) {
|
|
16793
|
+
const project2 = resolveProject(opts.db, request.params.name);
|
|
16794
|
+
opts.viewerSessions.reset(project2.name, viewerId);
|
|
16795
|
+
return { status: "reset" };
|
|
16796
|
+
}
|
|
16448
16797
|
requireInstanceAdministrator(request);
|
|
16449
16798
|
const project = resolveProject(opts.db, request.params.name);
|
|
16450
16799
|
if (opts.sessionRegistry.isBusy(project.name)) throw agentBusy(project.name);
|
|
16451
16800
|
opts.sessionRegistry.reset(project.name);
|
|
16452
|
-
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
16801
|
+
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq23(agentSessions.projectId, project.id)).run();
|
|
16453
16802
|
return { status: "reset" };
|
|
16454
16803
|
}
|
|
16455
16804
|
);
|
|
16456
|
-
app.post("/projects/:name/agent/prompt",
|
|
16457
|
-
|
|
16805
|
+
app.post("/projects/:name/agent/prompt", {
|
|
16806
|
+
// A turn is a paid read: it calls the LLM provider. The marker only lets a
|
|
16807
|
+
// read-only principal reach this handler; the handler decides. A viewer is
|
|
16808
|
+
// served from their own lane when the install allows it, and every other
|
|
16809
|
+
// caller still meets `requireInstanceAdministrator`.
|
|
16810
|
+
config: { paidRead: true }
|
|
16811
|
+
}, async (request, reply) => {
|
|
16812
|
+
const viewerId = viewerAeroCaller(request, opts);
|
|
16813
|
+
if (!viewerId) requireInstanceAdministrator(request);
|
|
16458
16814
|
const project = resolveProject(opts.db, request.params.name);
|
|
16459
16815
|
const parsed = agentPromptRequestSchema.safeParse(request.body);
|
|
16460
16816
|
if (!parsed.success) throw validationError(parsed.error.issues.map((issue) => issue.message).join("; "));
|
|
16461
16817
|
const body = parsed.data;
|
|
16818
|
+
if (viewerId) {
|
|
16819
|
+
if (body.prompt.length > VIEWER_AERO_MAX_PROMPT_CHARS) {
|
|
16820
|
+
throw validationError(`Keep questions under ${VIEWER_AERO_MAX_PROMPT_CHARS} characters.`);
|
|
16821
|
+
}
|
|
16822
|
+
return streamViewerTurn(opts, project, viewerId, body, reply);
|
|
16823
|
+
}
|
|
16462
16824
|
if (body.conversationId !== void 0) {
|
|
16463
|
-
const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(
|
|
16825
|
+
const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(eq23(agentSessions.projectId, project.id)).get();
|
|
16464
16826
|
if ((current?.id ?? null) !== body.conversationId) throw validationError("The active conversation changed. Reload the conversation before sending.");
|
|
16465
16827
|
}
|
|
16466
16828
|
const promptText = body.prompt;
|
|
@@ -16491,60 +16853,13 @@ function registerAgentRoutes(app, opts) {
|
|
|
16491
16853
|
throw err;
|
|
16492
16854
|
}
|
|
16493
16855
|
if (disconnected.signal.aborted) return reply;
|
|
16494
|
-
const
|
|
16495
|
-
|
|
16496
|
-
|
|
16497
|
-
|
|
16498
|
-
|
|
16499
|
-
|
|
16856
|
+
const pending = opts.sessionRegistry.consumePending(project.name);
|
|
16857
|
+
const userMessage = promptMessage(promptText, body.context);
|
|
16858
|
+
await streamAgentTurn(reply, agent, {
|
|
16859
|
+
batch: pending.length > 0 ? [...pending, userMessage] : userMessage,
|
|
16860
|
+
save: () => opts.sessionRegistry.save(project.name),
|
|
16861
|
+
detach: () => reply.raw.off("close", onClose)
|
|
16500
16862
|
});
|
|
16501
|
-
const write = (payload) => {
|
|
16502
|
-
if (reply.raw.writableEnded) return;
|
|
16503
|
-
try {
|
|
16504
|
-
reply.raw.write(`data: ${JSON.stringify(payload)}
|
|
16505
|
-
|
|
16506
|
-
`);
|
|
16507
|
-
} catch {
|
|
16508
|
-
}
|
|
16509
|
-
};
|
|
16510
|
-
write({ type: "stream_open" });
|
|
16511
|
-
const unsubscribe = agent.subscribe((event) => {
|
|
16512
|
-
if (event.type === "tool_execution_start") {
|
|
16513
|
-
const labelled = { ...event, label: acquiredAgent.state.tools.find((tool) => tool.name === event.toolName)?.label };
|
|
16514
|
-
write(labelled);
|
|
16515
|
-
} else write(event);
|
|
16516
|
-
});
|
|
16517
|
-
let failed = false;
|
|
16518
|
-
try {
|
|
16519
|
-
const pending = opts.sessionRegistry.consumePending(project.name);
|
|
16520
|
-
const userMessage = {
|
|
16521
|
-
role: "user",
|
|
16522
|
-
content: promptText,
|
|
16523
|
-
timestamp: Date.now(),
|
|
16524
|
-
...body.context ? { aeroContext: body.context } : {}
|
|
16525
|
-
};
|
|
16526
|
-
const batch = pending.length > 0 ? [...pending, userMessage] : userMessage;
|
|
16527
|
-
await agent.prompt(batch);
|
|
16528
|
-
await agent.waitForIdle();
|
|
16529
|
-
} catch (err) {
|
|
16530
|
-
failed = true;
|
|
16531
|
-
write({ type: "error", message: describeError(err) });
|
|
16532
|
-
} finally {
|
|
16533
|
-
try {
|
|
16534
|
-
opts.sessionRegistry.save(project.name);
|
|
16535
|
-
} catch (err) {
|
|
16536
|
-
failed = true;
|
|
16537
|
-
write({ type: "error", message: describeError(err) });
|
|
16538
|
-
}
|
|
16539
|
-
const status = aeroTurnStatus(agent);
|
|
16540
|
-
write({ type: "aero_turn_status", status: status && failed ? { ...status, reason: "error" } : status });
|
|
16541
|
-
unsubscribe();
|
|
16542
|
-
reply.raw.off("close", onClose);
|
|
16543
|
-
write({ type: "stream_close" });
|
|
16544
|
-
if (!reply.raw.writableEnded) {
|
|
16545
|
-
reply.raw.end();
|
|
16546
|
-
}
|
|
16547
|
-
}
|
|
16548
16863
|
return reply;
|
|
16549
16864
|
});
|
|
16550
16865
|
app.get(
|
|
@@ -17445,20 +17760,20 @@ function clipText(value, length) {
|
|
|
17445
17760
|
}
|
|
17446
17761
|
|
|
17447
17762
|
// src/research-runner.ts
|
|
17448
|
-
import { and as
|
|
17763
|
+
import { and as and18, eq as eq24, inArray as inArray9, sql as sql9 } from "drizzle-orm";
|
|
17449
17764
|
var unfinishedResearchQueryStatuses = [ResearchQueryStatuses.queued, ResearchQueryStatuses.running];
|
|
17450
17765
|
function finalResearchRunStatus(completed, failed) {
|
|
17451
17766
|
if (failed === 0) return ResearchRunStatuses.completed;
|
|
17452
17767
|
return completed > 0 ? ResearchRunStatuses.partial : ResearchRunStatuses.failed;
|
|
17453
17768
|
}
|
|
17454
17769
|
async function executeResearchRun(db, registry, runId, projectId) {
|
|
17455
|
-
const run = db.select().from(researchRuns).where(
|
|
17770
|
+
const run = db.select().from(researchRuns).where(and18(eq24(researchRuns.id, runId), eq24(researchRuns.projectId, projectId))).get();
|
|
17456
17771
|
if (!run || run.status !== ResearchRunStatuses.queued) return;
|
|
17457
|
-
const project = db.select().from(projects).where(
|
|
17772
|
+
const project = db.select().from(projects).where(eq24(projects.id, projectId)).get();
|
|
17458
17773
|
if (!project) return;
|
|
17459
17774
|
const provider = registry.get(run.provider);
|
|
17460
17775
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
17461
|
-
const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(
|
|
17776
|
+
const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(and18(eq24(researchRuns.id, runId), eq24(researchRuns.status, ResearchRunStatuses.queued))).run();
|
|
17462
17777
|
if (claim.changes !== 1) return;
|
|
17463
17778
|
let reserved = 0;
|
|
17464
17779
|
let dispatched = 0;
|
|
@@ -17482,8 +17797,8 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
17482
17797
|
}
|
|
17483
17798
|
reserved = run.totalQueries;
|
|
17484
17799
|
reservation = { scope, period };
|
|
17485
|
-
const rows = db.select().from(researchRunQueries).where(
|
|
17486
|
-
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(
|
|
17800
|
+
const rows = db.select().from(researchRunQueries).where(eq24(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
|
|
17801
|
+
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq24(competitors.projectId, projectId)).all().map((row) => row.domain);
|
|
17487
17802
|
const domains = effectiveDomains(project);
|
|
17488
17803
|
const brands = effectiveBrandNames(project);
|
|
17489
17804
|
const config = { ...provider.config, model: run.resolvedModel };
|
|
@@ -17491,7 +17806,7 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
17491
17806
|
await mapWithConcurrency(rows, Math.max(1, provider.config.quotaPolicy.maxConcurrency), async (row) => {
|
|
17492
17807
|
try {
|
|
17493
17808
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17494
|
-
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(
|
|
17809
|
+
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(and18(eq24(researchRunQueries.id, row.id), eq24(researchRunQueries.status, ResearchQueryStatuses.queued))).run();
|
|
17495
17810
|
const raw = await gate.run(async () => {
|
|
17496
17811
|
dispatched++;
|
|
17497
17812
|
return provider.adapter.executeTrackedQuery({ query: row.queryText, canonicalDomains: domains, competitorDomains, ...run.location ? { location: run.location } : {} }, config);
|
|
@@ -17518,7 +17833,7 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
17518
17833
|
citationState: determineCitationState(normalized, domains),
|
|
17519
17834
|
rawResponse: raw.rawResponse,
|
|
17520
17835
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17521
|
-
}).where(
|
|
17836
|
+
}).where(and18(eq24(researchRunQueries.id, row.id), eq24(researchRunQueries.status, ResearchQueryStatuses.running))).run();
|
|
17522
17837
|
if (completed.changes === 1) incrementResearchProgress(db, runId, "completedQueries");
|
|
17523
17838
|
} catch (error) {
|
|
17524
17839
|
try {
|
|
@@ -17542,18 +17857,18 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
17542
17857
|
}
|
|
17543
17858
|
}
|
|
17544
17859
|
function incrementResearchProgress(db, runId, column) {
|
|
17545
|
-
db.update(researchRuns).set({ [column]: sql9`${researchRuns[column]} + 1` }).where(
|
|
17860
|
+
db.update(researchRuns).set({ [column]: sql9`${researchRuns[column]} + 1` }).where(eq24(researchRuns.id, runId)).run();
|
|
17546
17861
|
}
|
|
17547
17862
|
function markResearchQueryFailed(db, runId, queryId, error) {
|
|
17548
17863
|
const message = describeError(error);
|
|
17549
|
-
const failed = db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error: message, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
17864
|
+
const failed = db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error: message, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and18(eq24(researchRunQueries.id, queryId), inArray9(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
|
|
17550
17865
|
if (failed.changes === 1) incrementResearchProgress(db, runId, "failedQueries");
|
|
17551
17866
|
}
|
|
17552
17867
|
function markUnfinishedResearchQueriesFailed(db, runId, error) {
|
|
17553
|
-
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
17868
|
+
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and18(eq24(researchRunQueries.researchRunId, runId), inArray9(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
|
|
17554
17869
|
}
|
|
17555
17870
|
function finalizeResearchRun(db, runId, fatalError) {
|
|
17556
|
-
const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(
|
|
17871
|
+
const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq24(researchRunQueries.researchRunId, runId)).all();
|
|
17557
17872
|
const completed = rows.filter((row) => row.status === ResearchQueryStatuses.completed).length;
|
|
17558
17873
|
const failed = rows.filter((row) => row.status === ResearchQueryStatuses.failed).length;
|
|
17559
17874
|
db.update(researchRuns).set({
|
|
@@ -17562,11 +17877,11 @@ function finalizeResearchRun(db, runId, fatalError) {
|
|
|
17562
17877
|
failedQueries: failed,
|
|
17563
17878
|
error: fatalError ?? (failed === rows.length ? "Every research query failed." : null),
|
|
17564
17879
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17565
|
-
}).where(
|
|
17880
|
+
}).where(eq24(researchRuns.id, runId)).run();
|
|
17566
17881
|
}
|
|
17567
17882
|
|
|
17568
17883
|
// src/google-marketing-sync.ts
|
|
17569
|
-
import { and as
|
|
17884
|
+
import { and as and19, eq as eq25, isNull as isNull2 } from "drizzle-orm";
|
|
17570
17885
|
function safeErrorMessage(error) {
|
|
17571
17886
|
const raw = describeError(error);
|
|
17572
17887
|
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);
|
|
@@ -17589,10 +17904,10 @@ function assertSnapshotContext(metadata2, expected) {
|
|
|
17589
17904
|
}
|
|
17590
17905
|
}
|
|
17591
17906
|
function requireSyncRun(db, runId, projectId, kind) {
|
|
17592
|
-
const run = db.select().from(runs).where(
|
|
17593
|
-
|
|
17594
|
-
|
|
17595
|
-
|
|
17907
|
+
const run = db.select().from(runs).where(and19(
|
|
17908
|
+
eq25(runs.id, runId),
|
|
17909
|
+
eq25(runs.projectId, projectId),
|
|
17910
|
+
eq25(runs.kind, kind)
|
|
17596
17911
|
)).get();
|
|
17597
17912
|
if (!run) throw new Error(`Run ${runId} is not a ${kind} run for this project.`);
|
|
17598
17913
|
if (run.status !== RunStatuses.queued) {
|
|
@@ -17605,19 +17920,19 @@ function markFailed(db, runId, projectId, kind, expectedStatus, error) {
|
|
|
17605
17920
|
status: RunStatuses.failed,
|
|
17606
17921
|
error: serializeRunError({ message: safeErrorMessage(error) }),
|
|
17607
17922
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17608
|
-
}).where(
|
|
17609
|
-
|
|
17610
|
-
|
|
17611
|
-
|
|
17612
|
-
|
|
17923
|
+
}).where(and19(
|
|
17924
|
+
eq25(runs.id, runId),
|
|
17925
|
+
eq25(runs.projectId, projectId),
|
|
17926
|
+
eq25(runs.kind, kind),
|
|
17927
|
+
eq25(runs.status, expectedStatus)
|
|
17613
17928
|
)).run();
|
|
17614
17929
|
}
|
|
17615
17930
|
function claimSyncRun(db, runId, projectId, kind, startedAt) {
|
|
17616
|
-
const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(
|
|
17617
|
-
|
|
17618
|
-
|
|
17619
|
-
|
|
17620
|
-
|
|
17931
|
+
const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(and19(
|
|
17932
|
+
eq25(runs.id, runId),
|
|
17933
|
+
eq25(runs.projectId, projectId),
|
|
17934
|
+
eq25(runs.kind, kind),
|
|
17935
|
+
eq25(runs.status, RunStatuses.queued)
|
|
17621
17936
|
)).run();
|
|
17622
17937
|
return claim.changes === 1;
|
|
17623
17938
|
}
|
|
@@ -17625,9 +17940,9 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
|
|
|
17625
17940
|
let claimed = false;
|
|
17626
17941
|
try {
|
|
17627
17942
|
requireSyncRun(db, runId, projectId, RunKinds["google-ads-sync"]);
|
|
17628
|
-
const project = db.select().from(projects).where(
|
|
17943
|
+
const project = db.select().from(projects).where(eq25(projects.id, projectId)).get();
|
|
17629
17944
|
if (!project) throw new Error(`Project ${projectId} was not found.`);
|
|
17630
|
-
const connection = db.select().from(googleAdsConnections).where(
|
|
17945
|
+
const connection = db.select().from(googleAdsConnections).where(eq25(googleAdsConnections.projectId, projectId)).get();
|
|
17631
17946
|
if (!connection?.selectedCustomerId) {
|
|
17632
17947
|
throw new Error("Google Ads needs a selected customer before it can sync.");
|
|
17633
17948
|
}
|
|
@@ -17678,11 +17993,11 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
|
|
|
17678
17993
|
status: RunStatuses.completed,
|
|
17679
17994
|
finishedAt,
|
|
17680
17995
|
error: null
|
|
17681
|
-
}).where(
|
|
17682
|
-
|
|
17683
|
-
|
|
17684
|
-
|
|
17685
|
-
|
|
17996
|
+
}).where(and19(
|
|
17997
|
+
eq25(runs.id, runId),
|
|
17998
|
+
eq25(runs.projectId, projectId),
|
|
17999
|
+
eq25(runs.kind, RunKinds["google-ads-sync"]),
|
|
18000
|
+
eq25(runs.status, RunStatuses.running)
|
|
17686
18001
|
)).run();
|
|
17687
18002
|
if (terminalClaim.changes !== 1) {
|
|
17688
18003
|
throw new Error(`Run ${runId} was no longer running when Google Ads evidence was ready.`);
|
|
@@ -17699,12 +18014,12 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
|
|
|
17699
18014
|
lastMetricsSnapshotAt: result2.metrics?.metadata.capturedAt ?? connection.lastMetricsSnapshotAt,
|
|
17700
18015
|
lastMetricsSnapshotId: result2.metrics?.metadata.id ?? connection.lastMetricsSnapshotId,
|
|
17701
18016
|
updatedAt: finishedAt
|
|
17702
|
-
}).where(
|
|
17703
|
-
|
|
17704
|
-
|
|
17705
|
-
|
|
17706
|
-
connection.selectedLoginCustomerId === null ? isNull2(googleAdsConnections.selectedLoginCustomerId) :
|
|
17707
|
-
|
|
18017
|
+
}).where(and19(
|
|
18018
|
+
eq25(googleAdsConnections.id, connection.id),
|
|
18019
|
+
eq25(googleAdsConnections.projectId, projectId),
|
|
18020
|
+
eq25(googleAdsConnections.selectedCustomerId, connection.selectedCustomerId),
|
|
18021
|
+
connection.selectedLoginCustomerId === null ? isNull2(googleAdsConnections.selectedLoginCustomerId) : eq25(googleAdsConnections.selectedLoginCustomerId, connection.selectedLoginCustomerId),
|
|
18022
|
+
eq25(googleAdsConnections.selectionGeneration, connection.selectionGeneration)
|
|
17708
18023
|
)).run();
|
|
17709
18024
|
if (connectionClaim.changes !== 1) {
|
|
17710
18025
|
throw new Error("Google Ads selection changed while its evidence was being collected.");
|
|
@@ -17731,9 +18046,9 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
17731
18046
|
let claimed = false;
|
|
17732
18047
|
try {
|
|
17733
18048
|
requireSyncRun(db, runId, projectId, RunKinds["gtm-sync"]);
|
|
17734
|
-
const project = db.select().from(projects).where(
|
|
18049
|
+
const project = db.select().from(projects).where(eq25(projects.id, projectId)).get();
|
|
17735
18050
|
if (!project) throw new Error(`Project ${projectId} was not found.`);
|
|
17736
|
-
const connection = db.select().from(gtmConnections).where(
|
|
18051
|
+
const connection = db.select().from(gtmConnections).where(eq25(gtmConnections.projectId, projectId)).get();
|
|
17737
18052
|
if (!connection?.selectedAccountId || !connection.selectedContainerId) {
|
|
17738
18053
|
throw new Error("GTM needs a selected account and container before it can sync.");
|
|
17739
18054
|
}
|
|
@@ -17768,11 +18083,11 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
17768
18083
|
status: RunStatuses.completed,
|
|
17769
18084
|
finishedAt,
|
|
17770
18085
|
error: null
|
|
17771
|
-
}).where(
|
|
17772
|
-
|
|
17773
|
-
|
|
17774
|
-
|
|
17775
|
-
|
|
18086
|
+
}).where(and19(
|
|
18087
|
+
eq25(runs.id, runId),
|
|
18088
|
+
eq25(runs.projectId, projectId),
|
|
18089
|
+
eq25(runs.kind, RunKinds["gtm-sync"]),
|
|
18090
|
+
eq25(runs.status, RunStatuses.running)
|
|
17776
18091
|
)).run();
|
|
17777
18092
|
if (terminalClaim.changes !== 1) {
|
|
17778
18093
|
throw new Error(`Run ${runId} was no longer running when GTM evidence was ready.`);
|
|
@@ -17786,13 +18101,13 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
17786
18101
|
lastSnapshotAt: snapshot.metadata.capturedAt,
|
|
17787
18102
|
lastSnapshotId: snapshot.metadata.id,
|
|
17788
18103
|
updatedAt: finishedAt
|
|
17789
|
-
}).where(
|
|
17790
|
-
|
|
17791
|
-
|
|
17792
|
-
|
|
17793
|
-
|
|
17794
|
-
connection.selectedWorkspaceId === null ? isNull2(gtmConnections.selectedWorkspaceId) :
|
|
17795
|
-
|
|
18104
|
+
}).where(and19(
|
|
18105
|
+
eq25(gtmConnections.id, connection.id),
|
|
18106
|
+
eq25(gtmConnections.projectId, projectId),
|
|
18107
|
+
eq25(gtmConnections.selectedAccountId, connection.selectedAccountId),
|
|
18108
|
+
eq25(gtmConnections.selectedContainerId, connection.selectedContainerId),
|
|
18109
|
+
connection.selectedWorkspaceId === null ? isNull2(gtmConnections.selectedWorkspaceId) : eq25(gtmConnections.selectedWorkspaceId, connection.selectedWorkspaceId),
|
|
18110
|
+
eq25(gtmConnections.selectionGeneration, connection.selectionGeneration)
|
|
17796
18111
|
)).run();
|
|
17797
18112
|
if (connectionClaim.changes !== 1) {
|
|
17798
18113
|
throw new Error("GTM selection changed while its evidence was being collected.");
|
|
@@ -17913,14 +18228,14 @@ function summarizeProviderConfig(config) {
|
|
|
17913
18228
|
};
|
|
17914
18229
|
}
|
|
17915
18230
|
function hashApiKey2(key) {
|
|
17916
|
-
return
|
|
18231
|
+
return crypto27.createHash("sha256").update(key).digest("hex");
|
|
17917
18232
|
}
|
|
17918
18233
|
var DASHBOARD_SCRYPT_KEYLEN = 64;
|
|
17919
18234
|
var DASHBOARD_SCRYPT_COST = 1 << 15;
|
|
17920
18235
|
var DASHBOARD_SCRYPT_MAXMEM = 64 * 1024 * 1024;
|
|
17921
18236
|
function hashDashboardPassword(password) {
|
|
17922
|
-
const salt =
|
|
17923
|
-
const derived =
|
|
18237
|
+
const salt = crypto27.randomBytes(16);
|
|
18238
|
+
const derived = crypto27.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
|
|
17924
18239
|
N: DASHBOARD_SCRYPT_COST,
|
|
17925
18240
|
maxmem: DASHBOARD_SCRYPT_MAXMEM
|
|
17926
18241
|
});
|
|
@@ -17941,14 +18256,14 @@ function verifyDashboardPassword(password, storedHash) {
|
|
|
17941
18256
|
} catch {
|
|
17942
18257
|
return { ok: false, needsRehash: false };
|
|
17943
18258
|
}
|
|
17944
|
-
const derived =
|
|
18259
|
+
const derived = crypto27.scryptSync(password, salt, expected.length, {
|
|
17945
18260
|
N: DASHBOARD_SCRYPT_COST,
|
|
17946
18261
|
maxmem: DASHBOARD_SCRYPT_MAXMEM
|
|
17947
18262
|
});
|
|
17948
18263
|
if (derived.length !== expected.length)
|
|
17949
18264
|
return { ok: false, needsRehash: false };
|
|
17950
18265
|
return {
|
|
17951
|
-
ok:
|
|
18266
|
+
ok: crypto27.timingSafeEqual(derived, expected),
|
|
17952
18267
|
needsRehash: false
|
|
17953
18268
|
};
|
|
17954
18269
|
}
|
|
@@ -17957,7 +18272,7 @@ function verifyDashboardPassword(password, storedHash) {
|
|
|
17957
18272
|
const expected = Buffer.from(storedHash, "hex");
|
|
17958
18273
|
if (candidate.length !== expected.length)
|
|
17959
18274
|
return { ok: false, needsRehash: false };
|
|
17960
|
-
const ok =
|
|
18275
|
+
const ok = crypto27.timingSafeEqual(candidate, expected);
|
|
17961
18276
|
return { ok, needsRehash: ok };
|
|
17962
18277
|
}
|
|
17963
18278
|
return { ok: false, needsRehash: false };
|
|
@@ -18093,7 +18408,7 @@ function cloneGtmConfig(config) {
|
|
|
18093
18408
|
function createGoogleMarketingConfigCredentialStore(input) {
|
|
18094
18409
|
const persistConfigPatch = input.saveConfigPatch ?? saveConfigPatch;
|
|
18095
18410
|
const env = input.env ?? process.env;
|
|
18096
|
-
const randomUUID2 = input.randomUUID ??
|
|
18411
|
+
const randomUUID2 = input.randomUUID ?? crypto27.randomUUID;
|
|
18097
18412
|
const snapshot = () => ({
|
|
18098
18413
|
googleAds: cloneGoogleAdsConfig(input.config.googleAds),
|
|
18099
18414
|
gtm: cloneGtmConfig(input.config.gtm)
|
|
@@ -18196,7 +18511,7 @@ async function createServer(opts) {
|
|
|
18196
18511
|
const trustProxy = resolveTrustProxy(process.env.CANONRY_TRUST_PROXY);
|
|
18197
18512
|
const app = Fastify({
|
|
18198
18513
|
loggerInstance: logger,
|
|
18199
|
-
genReqId: () =>
|
|
18514
|
+
genReqId: () => crypto27.randomUUID(),
|
|
18200
18515
|
trustProxy
|
|
18201
18516
|
});
|
|
18202
18517
|
const registry = new ProviderRegistry();
|
|
@@ -18281,6 +18596,13 @@ async function createServer(opts) {
|
|
|
18281
18596
|
proactive: agentProactive,
|
|
18282
18597
|
managedSweeps: dashboardManagedRunKinds.includes(SchedulableRunKinds["answer-visibility"])
|
|
18283
18598
|
}) : void 0;
|
|
18599
|
+
const agentAllowViewers = agentEnabled && resolveAgentAllowViewers(process.env, opts.config);
|
|
18600
|
+
const viewerAeroSessions = agentAllowViewers ? new ViewerAeroSessions({
|
|
18601
|
+
db: opts.db,
|
|
18602
|
+
config: opts.config,
|
|
18603
|
+
selfApiUrl: opts.config.apiUrl,
|
|
18604
|
+
managedSweeps: dashboardManagedRunKinds.includes(SchedulableRunKinds["answer-visibility"])
|
|
18605
|
+
}) : void 0;
|
|
18284
18606
|
const runCoordinator = new RunCoordinator(
|
|
18285
18607
|
opts.db,
|
|
18286
18608
|
notifier,
|
|
@@ -18289,7 +18611,7 @@ async function createServer(opts) {
|
|
|
18289
18611
|
async (ctx) => {
|
|
18290
18612
|
if (!sessionRegistry) return;
|
|
18291
18613
|
if (!agentProactive) return;
|
|
18292
|
-
const project = opts.db.select({ name: projects.name }).from(projects).where(
|
|
18614
|
+
const project = opts.db.select({ name: projects.name }).from(projects).where(eq26(projects.id, ctx.projectId)).get();
|
|
18293
18615
|
if (!project) return;
|
|
18294
18616
|
let content;
|
|
18295
18617
|
if (ctx.kind === RunKinds["aeo-discover-probe"]) {
|
|
@@ -18746,7 +19068,7 @@ async function createServer(opts) {
|
|
|
18746
19068
|
void (async () => {
|
|
18747
19069
|
try {
|
|
18748
19070
|
const report = await schedulerClient.runDoctor({ project: projectName });
|
|
18749
|
-
const project = opts.db.select().from(projects).where(
|
|
19071
|
+
const project = opts.db.select().from(projects).where(eq26(projects.name, projectName)).get();
|
|
18750
19072
|
if (!project) {
|
|
18751
19073
|
app.log.warn({ projectName }, "doctor schedule fired for an unknown project");
|
|
18752
19074
|
return;
|
|
@@ -18774,9 +19096,9 @@ async function createServer(opts) {
|
|
|
18774
19096
|
});
|
|
18775
19097
|
if (!probed) return;
|
|
18776
19098
|
const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(
|
|
18777
|
-
|
|
18778
|
-
|
|
18779
|
-
|
|
19099
|
+
and20(
|
|
19100
|
+
eq26(ccReleaseSyncs.release, probed.release),
|
|
19101
|
+
eq26(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
|
|
18780
19102
|
)
|
|
18781
19103
|
).limit(1).get();
|
|
18782
19104
|
if (alreadySynced) {
|
|
@@ -18957,7 +19279,7 @@ async function createServer(opts) {
|
|
|
18957
19279
|
return removed;
|
|
18958
19280
|
}
|
|
18959
19281
|
};
|
|
18960
|
-
const googleStateSecret = process.env.GOOGLE_STATE_SECRET ??
|
|
19282
|
+
const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto27.randomBytes(32).toString("hex");
|
|
18961
19283
|
const googleConnectionStore = {
|
|
18962
19284
|
listConnections: (domain) => listGoogleConnections(opts.config, domain),
|
|
18963
19285
|
getConnection: (domain, connectionType) => getGoogleConnection(opts.config, domain, connectionType),
|
|
@@ -19047,11 +19369,11 @@ async function createServer(opts) {
|
|
|
19047
19369
|
})();
|
|
19048
19370
|
if (opts.config.apiKey) {
|
|
19049
19371
|
const keyHash = hashApiKey2(opts.config.apiKey);
|
|
19050
|
-
const existing = opts.db.select().from(apiKeys).where(
|
|
19372
|
+
const existing = opts.db.select().from(apiKeys).where(eq26(apiKeys.keyHash, keyHash)).get();
|
|
19051
19373
|
if (!existing) {
|
|
19052
19374
|
const prefix = opts.config.apiKey.slice(0, 12);
|
|
19053
19375
|
opts.db.insert(apiKeys).values({
|
|
19054
|
-
id: `key_${
|
|
19376
|
+
id: `key_${crypto27.randomBytes(8).toString("hex")}`,
|
|
19055
19377
|
name: "default",
|
|
19056
19378
|
keyHash,
|
|
19057
19379
|
keyPrefix: prefix,
|
|
@@ -19075,7 +19397,7 @@ async function createServer(opts) {
|
|
|
19075
19397
|
};
|
|
19076
19398
|
const createSession = (apiKeyId) => {
|
|
19077
19399
|
pruneExpiredSessions();
|
|
19078
|
-
const sessionId =
|
|
19400
|
+
const sessionId = crypto27.randomBytes(32).toString("hex");
|
|
19079
19401
|
sessions.set(sessionId, {
|
|
19080
19402
|
apiKeyId,
|
|
19081
19403
|
expiresAt: Date.now() + SESSION_TTL_MS
|
|
@@ -19099,7 +19421,7 @@ async function createServer(opts) {
|
|
|
19099
19421
|
};
|
|
19100
19422
|
const getDefaultApiKey = () => {
|
|
19101
19423
|
if (!opts.config.apiKey) return void 0;
|
|
19102
|
-
return opts.db.select().from(apiKeys).where(
|
|
19424
|
+
return opts.db.select().from(apiKeys).where(eq26(apiKeys.keyHash, hashApiKey2(opts.config.apiKey))).get();
|
|
19103
19425
|
};
|
|
19104
19426
|
const createPasswordSession = (reply) => {
|
|
19105
19427
|
const key = getDefaultApiKey();
|
|
@@ -19123,7 +19445,7 @@ async function createServer(opts) {
|
|
|
19123
19445
|
if (!header) return false;
|
|
19124
19446
|
const parts = header.split(" ");
|
|
19125
19447
|
if (parts.length !== 2 || parts[0] !== "Bearer") return false;
|
|
19126
|
-
const key = opts.db.select().from(apiKeys).where(
|
|
19448
|
+
const key = opts.db.select().from(apiKeys).where(eq26(apiKeys.keyHash, hashApiKey2(parts[1]))).get();
|
|
19127
19449
|
return Boolean(key && !key.revokedAt);
|
|
19128
19450
|
};
|
|
19129
19451
|
const namedAccountsInUse = () => anyUsersExist(opts.db);
|
|
@@ -19213,12 +19535,12 @@ async function createServer(opts) {
|
|
|
19213
19535
|
return reply.send({ authenticated: true });
|
|
19214
19536
|
}
|
|
19215
19537
|
if (apiKey) {
|
|
19216
|
-
const key = opts.db.select().from(apiKeys).where(
|
|
19538
|
+
const key = opts.db.select().from(apiKeys).where(eq26(apiKeys.keyHash, hashApiKey2(apiKey))).get();
|
|
19217
19539
|
if (!key || key.revokedAt) {
|
|
19218
19540
|
const err2 = authInvalid();
|
|
19219
19541
|
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
19220
19542
|
}
|
|
19221
|
-
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
19543
|
+
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq26(apiKeys.id, key.id)).run();
|
|
19222
19544
|
const sessionId = createSession(key.id);
|
|
19223
19545
|
reply.header(
|
|
19224
19546
|
"set-cookie",
|
|
@@ -19371,7 +19693,7 @@ async function createServer(opts) {
|
|
|
19371
19693
|
});
|
|
19372
19694
|
registerOAuthAdminRoutes(scope, { db: opts.db });
|
|
19373
19695
|
if (!sessionRegistry) return;
|
|
19374
|
-
registerAgentRoutes(scope, { db: opts.db, sessionRegistry });
|
|
19696
|
+
registerAgentRoutes(scope, { db: opts.db, sessionRegistry, viewerSessions: viewerAeroSessions });
|
|
19375
19697
|
},
|
|
19376
19698
|
getGoogleAuthConfig: () => getGoogleAuthConfig(opts.config),
|
|
19377
19699
|
getPlacesConfig: () => getPlacesConfig(opts.config),
|
|
@@ -19381,8 +19703,8 @@ async function createServer(opts) {
|
|
|
19381
19703
|
getAgentPin: () => describeAgentPin(opts.config),
|
|
19382
19704
|
getGoogleMarketingDoctorInput: (ctx) => {
|
|
19383
19705
|
if (!ctx.project) return null;
|
|
19384
|
-
const googleAds = opts.db.select().from(googleAdsConnections).where(
|
|
19385
|
-
const gtm = opts.db.select().from(gtmConnections).where(
|
|
19706
|
+
const googleAds = opts.db.select().from(googleAdsConnections).where(eq26(googleAdsConnections.projectId, ctx.project.id)).get();
|
|
19707
|
+
const gtm = opts.db.select().from(gtmConnections).where(eq26(gtmConnections.projectId, ctx.project.id)).get();
|
|
19386
19708
|
const googleAdsCredential = getGoogleAdsConnection(opts.config, ctx.project.id);
|
|
19387
19709
|
const gtmCredential = getGtmConnection(opts.config, ctx.project.id);
|
|
19388
19710
|
const googleAdsConnected = Boolean(googleAdsCredential?.accessToken);
|
|
@@ -19506,7 +19828,7 @@ async function createServer(opts) {
|
|
|
19506
19828
|
deps: {
|
|
19507
19829
|
enqueueAutoExtract: ({ projectId, release: r }) => {
|
|
19508
19830
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19509
|
-
const runId =
|
|
19831
|
+
const runId = crypto27.randomUUID();
|
|
19510
19832
|
opts.db.insert(runs).values({
|
|
19511
19833
|
id: runId,
|
|
19512
19834
|
projectId,
|
|
@@ -19633,7 +19955,7 @@ async function createServer(opts) {
|
|
|
19633
19955
|
...inspectOpts,
|
|
19634
19956
|
config: opts.config
|
|
19635
19957
|
}).then(() => {
|
|
19636
|
-
const finished = opts.db.select({ status: runs.status }).from(runs).where(
|
|
19958
|
+
const finished = opts.db.select({ status: runs.status }).from(runs).where(eq26(runs.id, runId)).get();
|
|
19637
19959
|
if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
|
|
19638
19960
|
return maybeRefreshGscCoverage(
|
|
19639
19961
|
opts.db,
|
|
@@ -19751,7 +20073,7 @@ async function createServer(opts) {
|
|
|
19751
20073
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
19752
20074
|
opts.db.insert(auditLog).values(
|
|
19753
20075
|
targetProjectIds.map((projectId) => ({
|
|
19754
|
-
id:
|
|
20076
|
+
id: crypto27.randomUUID(),
|
|
19755
20077
|
projectId,
|
|
19756
20078
|
actor: auditContext?.actor ?? "api",
|
|
19757
20079
|
userAgent: auditContext?.userAgent ?? null,
|
|
@@ -19946,6 +20268,9 @@ async function createServer(opts) {
|
|
|
19946
20268
|
if (Object.keys(dashboardConfig).length > 0) {
|
|
19947
20269
|
clientConfig.dashboard = dashboardConfig;
|
|
19948
20270
|
}
|
|
20271
|
+
if (agentAllowViewers) {
|
|
20272
|
+
clientConfig.agent = { allowViewers: true };
|
|
20273
|
+
}
|
|
19949
20274
|
if (researchAllowViewers) {
|
|
19950
20275
|
clientConfig.research = {
|
|
19951
20276
|
allowViewers: true,
|
|
@@ -20106,7 +20431,7 @@ async function createServer(opts) {
|
|
|
20106
20431
|
},
|
|
20107
20432
|
notify: (projectId, result2) => notifier.onSiteLivenessChecked(projectId, result2)
|
|
20108
20433
|
});
|
|
20109
|
-
for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(
|
|
20434
|
+
for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq26(researchRuns.status, ResearchRunStatuses.queued)).all()) {
|
|
20110
20435
|
dispatchResearchRun(run.id, run.projectId);
|
|
20111
20436
|
}
|
|
20112
20437
|
runtimeStartupSettled = true;
|