@canonry/canonry 7.97.8 → 7.98.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/agent-workspace/skills/canonry/references/canonry-cli.md +2 -0
- package/assets/assets/{AgentSetupPage-DiX4GptZ.js → AgentSetupPage-h1kJhy9q.js} +1 -1
- package/assets/assets/{AuditHistoryPanel-de0zJkNC.js → AuditHistoryPanel-CNUApNiJ.js} +1 -1
- package/assets/assets/{BacklinksPage-BFfksR1M.js → BacklinksPage-CpKmlAek.js} +1 -1
- package/assets/assets/{HistoryPage-DQnGncRE.js → HistoryPage-DeN79d3c.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-DYCBQSnh.js → MeasurementPropertyPage-CcHnYn81.js} +1 -1
- package/assets/assets/{ProjectPage-BwwkYgfJ.js → ProjectPage-Tmuqmust.js} +1 -1
- package/assets/assets/{RunsPage-VeWCiQeM.js → RunsPage-BD4k3MjX.js} +1 -1
- package/assets/assets/{SettingsPage-BXPhnRMo.js → SettingsPage-BHy30JP5.js} +1 -1
- package/assets/assets/{SiteHealthSection-Ch9tNd8P.js → SiteHealthSection-SYdtuGjq.js} +2 -2
- package/assets/assets/ToneBadge-Cii5KNtg.js +60 -0
- package/assets/assets/{TrafficPage-CIzz7gmK.js → TrafficPage-DuxtRqnc.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-6IbzMhJ6.js → TrafficSourceDetailPage-DiX1bB02.js} +1 -1
- package/assets/assets/{account-context-DIWdmpj6.js → account-context-CdEHcI5S.js} +1 -1
- package/assets/assets/{agent-trust-copy-2Os0XoaQ.js → agent-trust-copy-N6WacCSg.js} +1 -1
- package/assets/assets/{card-CoYH9h6o.js → card-B1O0SunG.js} +1 -1
- package/assets/assets/{format-helpers-cRaGEIxc.js → format-helpers-V8Pyc7cR.js} +1 -1
- package/assets/assets/{index-vfNb458q.js → index-BTz-B9hV.js} +4 -4
- package/assets/assets/{query-client-Bf1oywTZ.js → query-client-C1ycmW1c.js} +1 -1
- package/assets/assets/{react-query-DYI08wkz.js → react-query-DI7VfOND.js} +1 -1
- package/assets/assets/{react-sigma_core.esm.min-BEhi6Q9o.js → react-sigma_core.esm.min-f4uuOgrw.js} +1 -1
- package/assets/assets/{server-traffic-tgMckYtk.js → server-traffic-CkSCFMnL.js} +1 -1
- package/assets/assets/{service-CN0y5nRe.js → service-DSgXWhNq.js} +1 -1
- package/assets/index.html +7 -7
- package/dist/{ads-F7ACQA6Z.js → ads-PQ4OP6OH.js} +3 -3
- package/dist/{agent-FBSRQSUX.js → agent-B6ZPL2HH.js} +7 -7
- package/dist/{agent-ask-VYSXDUCL.js → agent-ask-ZUKNXRAX.js} +3 -3
- package/dist/{agent-conversations-IKAS3RE6.js → agent-conversations-DJ7WU7PL.js} +3 -3
- package/dist/{agent-journeys-L3ABBCNC.js → agent-journeys-LGUI3SQG.js} +3 -3
- package/dist/{agent-memory-SKWZDBMQ.js → agent-memory-PWZJ7G5Q.js} +3 -3
- package/dist/{agent-providers-JBTFE5TJ.js → agent-providers-TEERDNJT.js} +3 -3
- package/dist/{agent-transcript-YGLXXFF7.js → agent-transcript-KDPOPK5Z.js} +3 -3
- package/dist/{analytics-ULDRQRRL.js → analytics-NFW5RQRT.js} +3 -3
- package/dist/{apply-KC4CCHY5.js → apply-EP4V4UF5.js} +3 -3
- package/dist/{backfill-5Y2GMEAH.js → backfill-6UTNNVFO.js} +10 -10
- package/dist/{backlinks-NXWU4BPY.js → backlinks-SG5GV5X5.js} +3 -3
- package/dist/{bing-E2KM2CW6.js → bing-Q4W24JNC.js} +3 -3
- package/dist/{bootstrap-QYWTDUIZ.js → bootstrap-WEMERVNZ.js} +10 -10
- package/dist/{cdp-E7WXCN5U.js → cdp-3XKBJGHO.js} +7 -7
- package/dist/{chunk-UBF2S3DF.js → chunk-2Q7JEC6N.js} +2 -2
- package/dist/{chunk-TUQYYF4G.js → chunk-2XCYEBQV.js} +2 -2
- package/dist/{chunk-TUBFKC3O.js → chunk-3BA75QYU.js} +1 -1
- package/dist/{chunk-LPSKHUCV.js → chunk-3GMBHGQ5.js} +3 -3
- package/dist/{chunk-4VSL2CMF.js → chunk-3JWY65XO.js} +7 -7
- package/dist/{chunk-RJNAUQSV.js → chunk-52M2JWCB.js} +1 -1
- package/dist/{chunk-7TIX5NHC.js → chunk-64KB6YTG.js} +3 -3
- package/dist/{chunk-YO2OW67H.js → chunk-64R66MWZ.js} +3 -3
- package/dist/{chunk-RSJBMQMN.js → chunk-BLW327AU.js} +1 -1
- package/dist/{chunk-WWGOMPYP.js → chunk-CN6MVUY3.js} +1 -1
- package/dist/{chunk-GMCVZPKO.js → chunk-EJZ444VV.js} +1 -1
- package/dist/{chunk-KSB3UZMW.js → chunk-ER5ZHAB2.js} +2 -2
- package/dist/{chunk-C3LS3ZKI.js → chunk-FQ3L2TIX.js} +1 -1
- package/dist/{chunk-JYG3LUXU.js → chunk-IAGC2MGP.js} +357 -357
- package/dist/{chunk-5455IRP3.js → chunk-ISIJ2NS4.js} +1 -1
- package/dist/{chunk-22LX5CWD.js → chunk-IX5AC3QB.js} +370 -218
- package/dist/{chunk-FDVCTZXD.js → chunk-J6MYB3F4.js} +1 -1
- package/dist/{chunk-66DTIEWN.js → chunk-KK3Z34AN.js} +28 -1
- package/dist/{chunk-7CAOGKRV.js → chunk-MSI4BGFN.js} +3 -3
- package/dist/{chunk-FZIYHP67.js → chunk-O22WSFWL.js} +27 -4
- package/dist/{chunk-7APKPDOV.js → chunk-OL2L5ANJ.js} +2 -2
- package/dist/{chunk-AI2G7QMW.js → chunk-Q5SNOMRB.js} +4 -4
- package/dist/{chunk-X4DR2LK7.js → chunk-QSL3FXE4.js} +4 -4
- package/dist/{chunk-MA3ZD7TF.js → chunk-RGFIRJ7F.js} +2 -2
- package/dist/{chunk-I6G5K5PM.js → chunk-RLKI2QF7.js} +6 -6
- package/dist/{chunk-QM4PMS52.js → chunk-RTH5JVC3.js} +1 -1
- package/dist/{chunk-NLN7EGU3.js → chunk-SAKH2RRF.js} +5 -5
- package/dist/{chunk-Q4KVK426.js → chunk-TNOYUE2L.js} +5 -5
- package/dist/{chunk-4OGRTTZX.js → chunk-UXXZXXNG.js} +2 -2
- package/dist/{chunk-NPCML7HY.js → chunk-VMGZBH5L.js} +3 -3
- package/dist/{chunk-5X2N5OOF.js → chunk-WZSDGC45.js} +1 -1
- package/dist/{chunk-7737JZP6.js → chunk-X3UXMHWC.js} +1 -1
- package/dist/{chunk-RABCSQBW.js → chunk-YJITDEZR.js} +4 -4
- package/dist/{chunk-MAWNTOQE.js → chunk-ZADRESLB.js} +2 -2
- package/dist/{chunk-ZN3KIIMP.js → chunk-ZBTUISPG.js} +11 -1
- package/dist/{citations-CJ5Y3ZET.js → citations-5OSQ3ZYU.js} +3 -3
- package/dist/{cli-catalog-Z4XQIDYU.js → cli-catalog-T5OAVKGS.js} +3 -3
- package/dist/{cli-commands-3HZFXKVU.js → cli-commands-VATCVJKW.js} +7 -7
- package/dist/cli.js +13 -13
- package/dist/{competitor-FC43JBCH.js → competitor-YIKTMTHY.js} +3 -3
- package/dist/{content-LS4EAVOS.js → content-DS2LKR7J.js} +3 -3
- package/dist/{dashboard-AZUFESTB.js → dashboard-HLVRRRI4.js} +6 -6
- package/dist/{demo-LTVTVYGC.js → demo-A5DTVJGK.js} +1 -1
- package/dist/{demo-server-WHDXTUWQ.js → demo-server-P2JGAI6C.js} +12 -12
- package/dist/{discover-T5D7T5EZ.js → discover-VNZDO2MU.js} +3 -3
- package/dist/{discover-eval-FQWKYZD7.js → discover-eval-ON77LUEN.js} +3 -3
- package/dist/{doctor-UEFC5WP7.js → doctor-7TNNTI7H.js} +3 -3
- package/dist/{evidence-FXGHON3E.js → evidence-E54CNGXS.js} +3 -3
- package/dist/{export-cmd-AF6KF2GP.js → export-cmd-XXZ6KLQQ.js} +3 -3
- package/dist/{ga-RMYK4AQC.js → ga-ZQB4EJI3.js} +3 -3
- package/dist/{gbp-5PAKGLBI.js → gbp-ZQL6NQEK.js} +3 -3
- package/dist/{google-6NYRRHIS.js → google-7EIHQGSV.js} +4 -4
- package/dist/{google-marketing-6EVLEHKD.js → google-marketing-RSAO4PG7.js} +1 -1
- package/dist/{health-cmd-WXDFHL7O.js → health-cmd-R7WPKGWX.js} +3 -3
- package/dist/{history-MZWTC4OE.js → history-2CSWSVCY.js} +3 -3
- package/dist/index.d.ts +115 -5
- package/dist/index.js +25 -25
- package/dist/{init-OM64S7AT.js → init-FXZRMXZ3.js} +12 -12
- package/dist/{insights-CYE3NXGD.js → insights-EJUL3W6D.js} +3 -3
- package/dist/intelligence-service-TKK7O3Z5.js +16 -0
- package/dist/{journeys-YISJ3GH4.js → journeys-YUMBANFH.js} +3 -3
- package/dist/{keys-L76I5EZI.js → keys-KLOTGT4H.js} +3 -3
- package/dist/{keyword-54UML7XE.js → keyword-5NSJFEOI.js} +3 -3
- package/dist/{logs-ZG44USRX.js → logs-WW3RLCRG.js} +3 -3
- package/dist/{mcp-T7ZNR55S.js → mcp-NTBEIEVX.js} +4 -4
- package/dist/mcp.js +9 -9
- package/dist/{measurement-plan-E7FAS5V3.js → measurement-plan-BLPAZEXS.js} +5 -5
- package/dist/{notify-ZB4V7B4L.js → notify-KAGE6UEN.js} +5 -4
- package/dist/{organic-evidence-W5BJ37VR.js → organic-evidence-HTYRXRML.js} +3 -3
- package/dist/{overview-MULDZS6B.js → overview-GSJ4GP2T.js} +4 -4
- package/dist/{project-LFDGHF3H.js → project-NYUWICVC.js} +3 -3
- package/dist/{query-ZV3XX5YW.js → query-3CRRDOHD.js} +3 -3
- package/dist/{research-HGAKJGVQ.js → research-ZULVD3TW.js} +3 -3
- package/dist/{results-export-5HCHJGBM.js → results-export-STM7SQ4J.js} +3 -3
- package/dist/{run-5VYP7S3V.js → run-S4MFU4MK.js} +4 -4
- package/dist/{schedule-FD2VIHX7.js → schedule-6W3Q2FUL.js} +3 -3
- package/dist/{search-6TJJWFJB.js → search-EKXXN7C4.js} +3 -3
- package/dist/{serve-4QZEQUER.js → serve-TFBFPFAQ.js} +26 -26
- package/dist/{session-PA4SLGYI.js → session-4B5V3AH6.js} +13 -13
- package/dist/{settings-NOXPXCAY.js → settings-VRTDPFR3.js} +3 -3
- package/dist/{sitemaps-P52HCBCM.js → sitemaps-R7XFCH6E.js} +3 -3
- package/dist/{skills-EBWXL2WN.js → skills-44XYCTVD.js} +2 -2
- package/dist/{snapshot-IST2UAPI.js → snapshot-ROAOJI4W.js} +3 -3
- package/dist/{src-53VQLBQC.js → src-ZJRIKEDB.js} +4 -2
- package/dist/{status-X2RYXIU5.js → status-EN3A2TX5.js} +4 -4
- package/dist/{technical-aeo-HRQRU2L3.js → technical-aeo-ZV466F65.js} +3 -3
- package/dist/{telemetry-KQATCLNR.js → telemetry-EZHPNZ2A.js} +4 -4
- package/dist/{tools-VLQ3GZUE.js → tools-IH4M7AAG.js} +11 -11
- package/dist/{traffic-WK6HEQWG.js → traffic-VFLXWB3Z.js} +5 -5
- package/dist/{trends-353NF4OU.js → trends-ZXJIX3Z3.js} +3 -3
- package/dist/{users-YXBFZHVO.js → users-J2BYEWOE.js} +3 -3
- package/dist/{visibility-stats-N4KZ3N4P.js → visibility-stats-AJ7NY2BP.js} +3 -3
- package/dist/{wordpress-3IPY5TA3.js → wordpress-U6MS35VE.js} +3 -3
- package/package.json +1 -1
- package/assets/assets/ToneBadge-JbnFjl09.js +0 -60
- package/dist/intelligence-service-ILIYUOKY.js +0 -16
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
answerServiceAdapters,
|
|
3
3
|
answerServiceDescriptors
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EJZ444VV.js";
|
|
5
5
|
import {
|
|
6
6
|
AeroLlmUsageFeatures,
|
|
7
7
|
aeroTurnStatus,
|
|
@@ -27,14 +27,14 @@ import {
|
|
|
27
27
|
resolveConfiguredAgentProvider,
|
|
28
28
|
resolveSessionProviderAndModel,
|
|
29
29
|
setAeroSystemPrompt
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-RLKI2QF7.js";
|
|
31
31
|
import {
|
|
32
32
|
AeroToolProfiles,
|
|
33
33
|
AeroToolScopes,
|
|
34
34
|
buildAeroStateTools,
|
|
35
35
|
isAeroToolProfile,
|
|
36
36
|
truncateToolResult
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-Q5SNOMRB.js";
|
|
38
38
|
import {
|
|
39
39
|
getGoogleAuthConfig,
|
|
40
40
|
getGoogleConnection,
|
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
removeCloudflareTrafficConnection,
|
|
54
54
|
removeCloudflareTrafficConnectionBySourceId,
|
|
55
55
|
upsertCloudflareTrafficConnection
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-J6MYB3F4.js";
|
|
57
57
|
import {
|
|
58
58
|
claudeAdapter,
|
|
59
59
|
embedQueries,
|
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
openrouterAdapter,
|
|
66
66
|
perplexityAdapter,
|
|
67
67
|
startTrafficReclassification
|
|
68
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-TNOYUE2L.js";
|
|
69
69
|
import {
|
|
70
70
|
classifyUsageSurface,
|
|
71
71
|
createApiUsageTelemetry,
|
|
@@ -77,7 +77,7 @@ import {
|
|
|
77
77
|
startOutcomeTimer,
|
|
78
78
|
trackFeatureCompleted,
|
|
79
79
|
trackInstallState
|
|
80
|
-
} from "./chunk-
|
|
80
|
+
} from "./chunk-YJITDEZR.js";
|
|
81
81
|
import {
|
|
82
82
|
isLoopbackBindHost
|
|
83
83
|
} from "./chunk-TSENU4GG.js";
|
|
@@ -85,7 +85,7 @@ import {
|
|
|
85
85
|
isApiProviderRegistrable,
|
|
86
86
|
isCdpProviderRegistrable,
|
|
87
87
|
registeredProviderNames
|
|
88
|
-
} from "./chunk-
|
|
88
|
+
} from "./chunk-FQ3L2TIX.js";
|
|
89
89
|
import {
|
|
90
90
|
checkLatestVersionForServer,
|
|
91
91
|
getOrCreateAnonymousId,
|
|
@@ -97,17 +97,17 @@ import {
|
|
|
97
97
|
recordDashboardEvent,
|
|
98
98
|
setTelemetryPreference,
|
|
99
99
|
trackEvent
|
|
100
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-2XCYEBQV.js";
|
|
101
101
|
import {
|
|
102
102
|
createCanonryMcpServer
|
|
103
|
-
} from "./chunk-
|
|
103
|
+
} from "./chunk-64R66MWZ.js";
|
|
104
104
|
import {
|
|
105
105
|
CANONRY_MCP_TIERS,
|
|
106
106
|
CANONRY_MCP_TOOLKIT_NAMES
|
|
107
107
|
} from "./chunk-MTW37GXF.js";
|
|
108
108
|
import {
|
|
109
109
|
ApiClient
|
|
110
|
-
} from "./chunk-
|
|
110
|
+
} from "./chunk-OL2L5ANJ.js";
|
|
111
111
|
import {
|
|
112
112
|
AGENT_PROVIDERS,
|
|
113
113
|
adapterSupportsBatch,
|
|
@@ -139,21 +139,21 @@ import {
|
|
|
139
139
|
saveConfigPatch,
|
|
140
140
|
sentimentInstallReadiness,
|
|
141
141
|
updateSharedProviderExecutionGate
|
|
142
|
-
} from "./chunk-
|
|
142
|
+
} from "./chunk-BLW327AU.js";
|
|
143
143
|
import {
|
|
144
144
|
getBundledSkillSnapshots
|
|
145
|
-
} from "./chunk-
|
|
145
|
+
} from "./chunk-RTH5JVC3.js";
|
|
146
146
|
import {
|
|
147
147
|
computeFactorAverages,
|
|
148
148
|
deleteSiteCrawlGraphLayout,
|
|
149
149
|
persistSiteCrawlGraphLayout,
|
|
150
150
|
prepareSiteCrawlGraphLayout,
|
|
151
151
|
recordedSharePct
|
|
152
|
-
} from "./chunk-
|
|
152
|
+
} from "./chunk-2Q7JEC6N.js";
|
|
153
153
|
import {
|
|
154
154
|
CanonryMcpToolNames,
|
|
155
155
|
canonryMcpTools
|
|
156
|
-
} from "./chunk-
|
|
156
|
+
} from "./chunk-3GMBHGQ5.js";
|
|
157
157
|
import {
|
|
158
158
|
PACKAGE_VERSION
|
|
159
159
|
} from "./chunk-AOJU5LKX.js";
|
|
@@ -162,16 +162,16 @@ import {
|
|
|
162
162
|
} from "./chunk-HGMYEDAQ.js";
|
|
163
163
|
import {
|
|
164
164
|
IntelligenceService
|
|
165
|
-
} from "./chunk-
|
|
165
|
+
} from "./chunk-SAKH2RRF.js";
|
|
166
166
|
import {
|
|
167
167
|
computeCitedCompetitorDomains,
|
|
168
168
|
determineCitationState
|
|
169
|
-
} from "./chunk-
|
|
169
|
+
} from "./chunk-ISIJ2NS4.js";
|
|
170
170
|
import {
|
|
171
171
|
addLogListener,
|
|
172
172
|
createFastifyLogger,
|
|
173
173
|
createLogger
|
|
174
|
-
} from "./chunk-
|
|
174
|
+
} from "./chunk-UXXZXXNG.js";
|
|
175
175
|
import {
|
|
176
176
|
BingApiError,
|
|
177
177
|
CC_CACHE_DIR,
|
|
@@ -316,7 +316,7 @@ import {
|
|
|
316
316
|
webhookResponseReason,
|
|
317
317
|
webhookTargetRefusalReason,
|
|
318
318
|
writeAuditLog
|
|
319
|
-
} from "./chunk-
|
|
319
|
+
} from "./chunk-O22WSFWL.js";
|
|
320
320
|
import {
|
|
321
321
|
OperationalLogStore,
|
|
322
322
|
SentimentIdempotencyConflict,
|
|
@@ -407,13 +407,14 @@ import {
|
|
|
407
407
|
siteCrawlPages,
|
|
408
408
|
siteCrawlRunRequests,
|
|
409
409
|
siteCrawlSnapshots,
|
|
410
|
+
siteHealthNotifyState,
|
|
410
411
|
siteLivenessState,
|
|
411
412
|
sitemapListSyncs,
|
|
412
413
|
trafficSources,
|
|
413
414
|
unrecognizedAgentEventsHourly,
|
|
414
415
|
unrecognizedAgentNotifyState,
|
|
415
416
|
usageCounters
|
|
416
|
-
} from "./chunk-
|
|
417
|
+
} from "./chunk-KK3Z34AN.js";
|
|
417
418
|
import {
|
|
418
419
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
419
420
|
AI_ENGINE_DOMAINS,
|
|
@@ -673,7 +674,7 @@ import {
|
|
|
673
674
|
winnabilityClassLabel,
|
|
674
675
|
withRetry,
|
|
675
676
|
withSkippedProviders
|
|
676
|
-
} from "./chunk-
|
|
677
|
+
} from "./chunk-ZBTUISPG.js";
|
|
677
678
|
|
|
678
679
|
// src/google-marketing-runtime.ts
|
|
679
680
|
import crypto2 from "crypto";
|
|
@@ -3357,13 +3358,13 @@ function createGoogleMarketingRuntime(options) {
|
|
|
3357
3358
|
|
|
3358
3359
|
// src/server.ts
|
|
3359
3360
|
import { createRequire } from "module";
|
|
3360
|
-
import
|
|
3361
|
+
import crypto28 from "crypto";
|
|
3361
3362
|
import fs5 from "fs";
|
|
3362
3363
|
import path5 from "path";
|
|
3363
3364
|
import { isIP as isIP2 } from "net";
|
|
3364
3365
|
import { fileURLToPath } from "url";
|
|
3365
3366
|
import { setImmediate as yieldToEventLoop4 } from "timers/promises";
|
|
3366
|
-
import { and as
|
|
3367
|
+
import { and as and30, eq as eq36, isNotNull as isNotNull5, isNull as isNull8, lte as lte3, max, ne as ne4, or as or4, sql as sql15 } from "drizzle-orm";
|
|
3367
3368
|
|
|
3368
3369
|
// src/seo-runtime.ts
|
|
3369
3370
|
import { eq, inArray } from "drizzle-orm";
|
|
@@ -6001,7 +6002,7 @@ function nonBlank(value) {
|
|
|
6001
6002
|
return trimmed ? trimmed : void 0;
|
|
6002
6003
|
}
|
|
6003
6004
|
function resolveBuildCommit(env = process.env) {
|
|
6004
|
-
const embedded = true ? "
|
|
6005
|
+
const embedded = true ? "161453e7b8d6e3b33614392ce97134fc484106ec" : void 0;
|
|
6005
6006
|
return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
|
|
6006
6007
|
}
|
|
6007
6008
|
function resolveInstanceIdentity(env = process.env) {
|
|
@@ -15139,8 +15140,85 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
|
|
|
15139
15140
|
}
|
|
15140
15141
|
|
|
15141
15142
|
// src/notifier.ts
|
|
15142
|
-
import { eq as
|
|
15143
|
+
import { eq as eq26, desc as desc10, and as and23, inArray as inArray12, or as or2, isNull as isNull5 } from "drizzle-orm";
|
|
15144
|
+
import crypto23 from "crypto";
|
|
15145
|
+
|
|
15146
|
+
// src/site-health-alerts.ts
|
|
15143
15147
|
import crypto22 from "crypto";
|
|
15148
|
+
import { and as and22, asc as asc5, desc as desc9, eq as eq25, gt as gt3 } from "drizzle-orm";
|
|
15149
|
+
var ISSUE_TITLES = {
|
|
15150
|
+
"missing-h1": "Missing H1",
|
|
15151
|
+
"multiple-h1": "Multiple H1 tags",
|
|
15152
|
+
"missing-title": "Missing title",
|
|
15153
|
+
"names-another-entity": "Names another property"
|
|
15154
|
+
};
|
|
15155
|
+
var PAGE_BATCH = 500;
|
|
15156
|
+
var MAX_EXAMPLES = 5;
|
|
15157
|
+
function siteHealthProblemKey(code, url) {
|
|
15158
|
+
return `${code}|${crypto22.createHash("sha256").update(url).digest("hex").slice(0, 16)}`;
|
|
15159
|
+
}
|
|
15160
|
+
function readSiteHealthScan(db, projectId, runId) {
|
|
15161
|
+
const snapshot = db.select().from(siteCrawlSnapshots).where(and22(eq25(siteCrawlSnapshots.projectId, projectId), eq25(siteCrawlSnapshots.runId, runId))).orderBy(desc9(siteCrawlSnapshots.updatedAt)).limit(1).get();
|
|
15162
|
+
if (!snapshot?.detailsAvailable || !snapshot.attemptId) return null;
|
|
15163
|
+
const scope = and22(
|
|
15164
|
+
eq25(siteCrawlPages.projectId, projectId),
|
|
15165
|
+
eq25(siteCrawlPages.runId, runId),
|
|
15166
|
+
eq25(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
15167
|
+
);
|
|
15168
|
+
const pagesByCode = /* @__PURE__ */ new Map();
|
|
15169
|
+
const keys = /* @__PURE__ */ new Set();
|
|
15170
|
+
let after;
|
|
15171
|
+
for (; ; ) {
|
|
15172
|
+
const pages = db.select({ nodeKey: siteCrawlPages.nodeKey, url: siteCrawlPages.url, auditFields: siteCrawlPages.auditFields }).from(siteCrawlPages).where(after === void 0 ? scope : and22(scope, gt3(siteCrawlPages.nodeKey, after))).orderBy(asc5(siteCrawlPages.nodeKey)).limit(PAGE_BATCH).all();
|
|
15173
|
+
for (const page of pages) {
|
|
15174
|
+
const defects = page.auditFields.criticalDefects;
|
|
15175
|
+
for (const defect of Array.isArray(defects) ? defects : []) {
|
|
15176
|
+
if (!defect || typeof defect !== "object") continue;
|
|
15177
|
+
const { id, severity } = defect;
|
|
15178
|
+
if (typeof id !== "string" || id === "" || severity !== "critical") continue;
|
|
15179
|
+
const key = siteHealthProblemKey(id, page.url);
|
|
15180
|
+
keys.add(key);
|
|
15181
|
+
let pagesForCode = pagesByCode.get(id);
|
|
15182
|
+
if (!pagesForCode) {
|
|
15183
|
+
pagesForCode = /* @__PURE__ */ new Map();
|
|
15184
|
+
pagesByCode.set(id, pagesForCode);
|
|
15185
|
+
}
|
|
15186
|
+
pagesForCode.set(key, page.url);
|
|
15187
|
+
}
|
|
15188
|
+
}
|
|
15189
|
+
if (pages.length < PAGE_BATCH) break;
|
|
15190
|
+
after = pages.at(-1).nodeKey;
|
|
15191
|
+
}
|
|
15192
|
+
return { complete: snapshot.complete, pagesByCode, keys };
|
|
15193
|
+
}
|
|
15194
|
+
function newSiteHealthIssues(scan, reported) {
|
|
15195
|
+
const issues = [];
|
|
15196
|
+
for (const [code, pages] of scan.pagesByCode) {
|
|
15197
|
+
const fresh = [...pages].filter(([key]) => !reported.has(key)).map(([, url]) => url).sort(compareText);
|
|
15198
|
+
if (fresh.length === 0) continue;
|
|
15199
|
+
issues.push({
|
|
15200
|
+
code,
|
|
15201
|
+
title: ISSUE_TITLES[code] ?? code,
|
|
15202
|
+
severity: "critical",
|
|
15203
|
+
affectedPages: pages.size,
|
|
15204
|
+
newPages: fresh.length,
|
|
15205
|
+
examples: fresh.slice(0, MAX_EXAMPLES)
|
|
15206
|
+
});
|
|
15207
|
+
}
|
|
15208
|
+
return issues.sort((a, b) => b.newPages - a.newPages || compareText(a.code, b.code));
|
|
15209
|
+
}
|
|
15210
|
+
function settledSiteHealthKeys(scan, reported) {
|
|
15211
|
+
return [...scan.complete ? scan.keys : /* @__PURE__ */ new Set([...reported, ...scan.keys])].sort(compareText);
|
|
15212
|
+
}
|
|
15213
|
+
function readSiteHealthNotifyState(db, projectId) {
|
|
15214
|
+
return db.select().from(siteHealthNotifyState).where(eq25(siteHealthNotifyState.projectId, projectId)).get() ?? null;
|
|
15215
|
+
}
|
|
15216
|
+
function writeSiteHealthNotifyState(db, input) {
|
|
15217
|
+
const values = { reportedKeys: input.reportedKeys, runId: input.runId, updatedAt: input.updatedAt, notifiedAt: input.notifiedAt };
|
|
15218
|
+
db.insert(siteHealthNotifyState).values({ projectId: input.projectId, ...values }).onConflictDoUpdate({ target: siteHealthNotifyState.projectId, set: values }).run();
|
|
15219
|
+
}
|
|
15220
|
+
|
|
15221
|
+
// src/notifier.ts
|
|
15144
15222
|
var log24 = createLogger("Notifier");
|
|
15145
15223
|
var sampleDelivery = createOutcomeSampler({ burst: 20, refillMs: 6e4, now: () => Date.now() });
|
|
15146
15224
|
function trackWebhookDelivery(input) {
|
|
@@ -15262,18 +15340,18 @@ var Notifier = class {
|
|
|
15262
15340
|
/** Called after a run completes (success, partial, or failed). */
|
|
15263
15341
|
async onRunCompleted(runId, projectId, opts) {
|
|
15264
15342
|
log24.info("run.completed", { runId, projectId, origin: opts?.origin ?? "sweep" });
|
|
15265
|
-
const notifs = this.db.select().from(notifications).where(
|
|
15343
|
+
const notifs = this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
15266
15344
|
if (notifs.length === 0) {
|
|
15267
15345
|
log24.info("notifications.none-enabled", { projectId });
|
|
15268
15346
|
return;
|
|
15269
15347
|
}
|
|
15270
15348
|
log24.info("notifications.found", { projectId, count: notifs.length });
|
|
15271
|
-
const run = this.db.select().from(runs).where(
|
|
15349
|
+
const run = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
|
|
15272
15350
|
if (!run) {
|
|
15273
15351
|
log24.error("run.not-found", { runId, msg: "skipping notification dispatch" });
|
|
15274
15352
|
return;
|
|
15275
15353
|
}
|
|
15276
|
-
const project = this.db.select().from(projects).where(
|
|
15354
|
+
const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
15277
15355
|
if (!project) {
|
|
15278
15356
|
log24.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
|
|
15279
15357
|
return;
|
|
@@ -15352,7 +15430,7 @@ var Notifier = class {
|
|
|
15352
15430
|
* than inferred from the absence of a webhook.
|
|
15353
15431
|
*/
|
|
15354
15432
|
async onHealthChecked(projectId, report) {
|
|
15355
|
-
const project = this.db.select().from(projects).where(
|
|
15433
|
+
const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
15356
15434
|
if (!project) {
|
|
15357
15435
|
log24.error("project.not-found", { projectId, msg: "skipping health notification" });
|
|
15358
15436
|
return null;
|
|
@@ -15385,7 +15463,7 @@ var Notifier = class {
|
|
|
15385
15463
|
const status = noSignal ? "warn" : worst ? worst.status : "ok";
|
|
15386
15464
|
const code = noSignal ? HEALTH_NO_SIGNAL_CODE : worst?.code ?? "health.ok";
|
|
15387
15465
|
const summary = noSignal ? `No health check produced a result (${healthChecks.length} skipped) \u2014 health is unknown, not confirmed.` : worst?.summary ?? `All ${graded.length} health check(s) passing.`;
|
|
15388
|
-
const previous = this.db.select().from(doctorHealthState).where(
|
|
15466
|
+
const previous = this.db.select().from(doctorHealthState).where(eq26(doctorHealthState.projectId, projectId)).get();
|
|
15389
15467
|
const previousStatus = previous?.status ?? null;
|
|
15390
15468
|
const failingSignature = failing.map((c) => `${c.status}:${c.code}`).join(",");
|
|
15391
15469
|
const failingNow = failing.map((c) => ({ id: c.id, code: c.code, rank: rank(c.status) }));
|
|
@@ -15455,7 +15533,7 @@ var Notifier = class {
|
|
|
15455
15533
|
const urgentById = new Map(urgentItems.map((item) => [item.id, item]));
|
|
15456
15534
|
const generalEvent = event;
|
|
15457
15535
|
const urgentRecord = previous?.urgentNotified ?? null;
|
|
15458
|
-
const enabled = generalEvent || urgentItems.length > 0 || urgentRecord ? this.db.select().from(notifications).where(
|
|
15536
|
+
const enabled = generalEvent || urgentItems.length > 0 || urgentRecord ? this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
|
|
15459
15537
|
const listedEvents = (notif) => notif.config.events;
|
|
15460
15538
|
const hasUrl = (notif) => Boolean(notif.config.url);
|
|
15461
15539
|
const urgentSubscribers = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
|
|
@@ -15503,7 +15581,7 @@ var Notifier = class {
|
|
|
15503
15581
|
if (previous === void 0) {
|
|
15504
15582
|
this.db.insert(doctorHealthState).values({ ...observation, notifiedAt: null }).run();
|
|
15505
15583
|
} else {
|
|
15506
|
-
this.db.update(doctorHealthState).set(observation).where(
|
|
15584
|
+
this.db.update(doctorHealthState).set(observation).where(eq26(doctorHealthState.projectId, projectId)).run();
|
|
15507
15585
|
}
|
|
15508
15586
|
for (const group of urgentGroups) {
|
|
15509
15587
|
const { urgentEvent, urgentTold, urgentNew, urgentCleared, urgentNotifs, urgentSettled } = group;
|
|
@@ -15547,7 +15625,7 @@ var Notifier = class {
|
|
|
15547
15625
|
}
|
|
15548
15626
|
if (accepted > 0) {
|
|
15549
15627
|
recordUrgentReceipts(urgentReceipts, group.subscribers, formatUrgentBreaches(urgentSettled) || null);
|
|
15550
|
-
this.db.update(doctorHealthState).set({ urgentNotified: doctorUrgentSummary(urgentReceipts), urgentReceipts }).where(
|
|
15628
|
+
this.db.update(doctorHealthState).set({ urgentNotified: doctorUrgentSummary(urgentReceipts), urgentReceipts }).where(eq26(doctorHealthState.projectId, projectId)).run();
|
|
15551
15629
|
}
|
|
15552
15630
|
if (urgentNotifs.length > 0) {
|
|
15553
15631
|
log24.info("health.urgent-notified", {
|
|
@@ -15591,7 +15669,7 @@ var Notifier = class {
|
|
|
15591
15669
|
if (await this.sendWebhook(config.url, payload, notif.id, projectId, notif.webhookSecret ?? null)) delivered += 1;
|
|
15592
15670
|
}
|
|
15593
15671
|
if (delivered > 0) {
|
|
15594
|
-
this.db.update(doctorHealthState).set({ notifiedAt: now, notifiedSignature: formatBreaches(failingNow), pendingSignature: null }).where(
|
|
15672
|
+
this.db.update(doctorHealthState).set({ notifiedAt: now, notifiedSignature: formatBreaches(failingNow), pendingSignature: null }).where(eq26(doctorHealthState.projectId, projectId)).run();
|
|
15595
15673
|
}
|
|
15596
15674
|
log24.info("health.notified", {
|
|
15597
15675
|
projectId,
|
|
@@ -15621,12 +15699,12 @@ var Notifier = class {
|
|
|
15621
15699
|
async onSiteLivenessChecked(projectId, result) {
|
|
15622
15700
|
const { check, checkedAt } = result;
|
|
15623
15701
|
if (check.status !== "ok" && check.status !== "fail") return null;
|
|
15624
|
-
const project = this.db.select().from(projects).where(
|
|
15702
|
+
const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
15625
15703
|
if (!project) {
|
|
15626
15704
|
log24.error("project.not-found", { projectId, msg: "skipping site liveness notification" });
|
|
15627
15705
|
return null;
|
|
15628
15706
|
}
|
|
15629
|
-
const previous = this.db.select().from(siteLivenessState).where(
|
|
15707
|
+
const previous = this.db.select().from(siteLivenessState).where(eq26(siteLivenessState.projectId, projectId)).get();
|
|
15630
15708
|
const sincePrevious = previous ? Date.parse(checkedAt) - Date.parse(previous.checkedAt) : Number.NaN;
|
|
15631
15709
|
const countsAsRepeat = Number.isFinite(sincePrevious) && sincePrevious >= SITE_LIVENESS_MIN_PASS_GAP_MS && sincePrevious <= SITE_LIVENESS_MAX_PASS_GAP_MS;
|
|
15632
15710
|
const failures = check.status === "fail" ? countsAsRepeat ? (previous?.consecutiveFailures ?? 0) + 1 : 1 : 0;
|
|
@@ -15636,7 +15714,7 @@ var Notifier = class {
|
|
|
15636
15714
|
if (check.status === "ok" && outagePaged) event = "health.recovered";
|
|
15637
15715
|
const confirmed = check.status === "fail" && failures >= SITE_LIVENESS_FAILURES_TO_PAGE;
|
|
15638
15716
|
const urgentRecord = previous?.urgentNotifiedAt ?? null;
|
|
15639
|
-
const enabled = event !== null || confirmed || urgentRecord !== null ? this.db.select().from(notifications).where(
|
|
15717
|
+
const enabled = event !== null || confirmed || urgentRecord !== null ? this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
|
|
15640
15718
|
const listedEvents = (notif) => notif.config.events;
|
|
15641
15719
|
const hasUrl = (notif) => Boolean(notif.config.url);
|
|
15642
15720
|
const scoped = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
|
|
@@ -15669,7 +15747,7 @@ var Notifier = class {
|
|
|
15669
15747
|
if (previous === void 0) {
|
|
15670
15748
|
this.db.insert(siteLivenessState).values(observation).run();
|
|
15671
15749
|
} else {
|
|
15672
|
-
this.db.update(siteLivenessState).set(observation).where(
|
|
15750
|
+
this.db.update(siteLivenessState).set(observation).where(eq26(siteLivenessState.projectId, projectId)).run();
|
|
15673
15751
|
}
|
|
15674
15752
|
if (!event && urgentGroups.every((group) => group.urgentEvent === null)) return null;
|
|
15675
15753
|
const payloadFor = (name) => {
|
|
@@ -15705,7 +15783,7 @@ var Notifier = class {
|
|
|
15705
15783
|
}
|
|
15706
15784
|
}
|
|
15707
15785
|
if (event === "health.degraded" && delivered > 0) {
|
|
15708
|
-
this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(
|
|
15786
|
+
this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(eq26(siteLivenessState.projectId, projectId)).run();
|
|
15709
15787
|
}
|
|
15710
15788
|
log24.info("site-liveness.notified", { projectId, event, code: check.code, subscribers: notifs.length, delivered });
|
|
15711
15789
|
}
|
|
@@ -15719,7 +15797,7 @@ var Notifier = class {
|
|
|
15719
15797
|
}
|
|
15720
15798
|
if (accepted > 0) {
|
|
15721
15799
|
recordUrgentReceipts(urgentReceipts, group.subscribers, group.settled);
|
|
15722
|
-
this.db.update(siteLivenessState).set({ urgentNotifiedAt: siteUrgentSummary(urgentReceipts), urgentReceipts }).where(
|
|
15800
|
+
this.db.update(siteLivenessState).set({ urgentNotifiedAt: siteUrgentSummary(urgentReceipts), urgentReceipts }).where(eq26(siteLivenessState.projectId, projectId)).run();
|
|
15723
15801
|
}
|
|
15724
15802
|
if (urgentNotifs.length > 0 || generalIds.size > 0) {
|
|
15725
15803
|
log24.info("site-liveness.urgent-notified", { projectId, event: urgentEvent, code: check.code, subscribers: urgentNotifs.length + generalIds.size, delivered: accepted });
|
|
@@ -15745,7 +15823,7 @@ var Notifier = class {
|
|
|
15745
15823
|
*/
|
|
15746
15824
|
isNewInsightNews(projectId, insight) {
|
|
15747
15825
|
const key = insightNotifyKey(projectId, insight);
|
|
15748
|
-
const previous = this.db.select().from(insightNotifyState).where(
|
|
15826
|
+
const previous = this.db.select().from(insightNotifyState).where(eq26(insightNotifyState.key, key)).get();
|
|
15749
15827
|
if (!previous) return true;
|
|
15750
15828
|
if (severityRank(insight.severity) > severityRank(previous.severity)) return true;
|
|
15751
15829
|
const magnitude = insightMagnitude(insight);
|
|
@@ -15767,9 +15845,9 @@ var Notifier = class {
|
|
|
15767
15845
|
magnitude: insightMagnitude(insight),
|
|
15768
15846
|
notifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15769
15847
|
};
|
|
15770
|
-
const existing = this.db.select().from(insightNotifyState).where(
|
|
15848
|
+
const existing = this.db.select().from(insightNotifyState).where(eq26(insightNotifyState.key, key)).get();
|
|
15771
15849
|
if (existing) {
|
|
15772
|
-
this.db.update(insightNotifyState).set(row).where(
|
|
15850
|
+
this.db.update(insightNotifyState).set(row).where(eq26(insightNotifyState.key, key)).run();
|
|
15773
15851
|
} else {
|
|
15774
15852
|
this.db.insert(insightNotifyState).values(row).run();
|
|
15775
15853
|
}
|
|
@@ -15783,11 +15861,11 @@ var Notifier = class {
|
|
|
15783
15861
|
if (criticalInsights.length > 0) insightEvents.push("insight.critical");
|
|
15784
15862
|
if (highInsights.length > 0) insightEvents.push("insight.high");
|
|
15785
15863
|
if (insightEvents.length === 0) return;
|
|
15786
|
-
const notifs = this.db.select().from(notifications).where(
|
|
15864
|
+
const notifs = this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
15787
15865
|
if (notifs.length === 0) return;
|
|
15788
|
-
const run = this.db.select().from(runs).where(
|
|
15866
|
+
const run = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
|
|
15789
15867
|
if (!run) return;
|
|
15790
|
-
const project = this.db.select().from(projects).where(
|
|
15868
|
+
const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
15791
15869
|
if (!project) return;
|
|
15792
15870
|
for (const notif of notifs) {
|
|
15793
15871
|
const config = notif.config;
|
|
@@ -15868,9 +15946,9 @@ var Notifier = class {
|
|
|
15868
15946
|
return delivered;
|
|
15869
15947
|
}
|
|
15870
15948
|
async dispatchQueuedReviewAlerts(projectId) {
|
|
15871
|
-
const pendingReviews = this.db.select().from(gbpReviews).where(
|
|
15872
|
-
const pendingRatings = this.db.select().from(gbpReviewRatings).where(
|
|
15873
|
-
const project = this.db.select().from(projects).where(
|
|
15949
|
+
const pendingReviews = this.db.select().from(gbpReviews).where(and23(eq26(gbpReviews.projectId, projectId), eq26(gbpReviews.alertState, "pending"))).all();
|
|
15950
|
+
const pendingRatings = this.db.select().from(gbpReviewRatings).where(and23(eq26(gbpReviewRatings.projectId, projectId), eq26(gbpReviewRatings.alertState, "pending"))).all();
|
|
15951
|
+
const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
15874
15952
|
if (!project) return { reviews: 0, ratingDrops: 0, delivered: 0 };
|
|
15875
15953
|
const chatSendsLeft = /* @__PURE__ */ new Map();
|
|
15876
15954
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -15882,10 +15960,10 @@ var Notifier = class {
|
|
|
15882
15960
|
displayName: gbpLocations.displayName,
|
|
15883
15961
|
mapsUri: gbpLocations.mapsUri,
|
|
15884
15962
|
reviewsAccess: gbpLocations.reviewsAccess
|
|
15885
|
-
}).from(gbpLocations).where(
|
|
15963
|
+
}).from(gbpLocations).where(eq26(gbpLocations.projectId, projectId)).all().map((loc) => [loc.locationName, loc])
|
|
15886
15964
|
);
|
|
15887
15965
|
const v4HasWorked = new Set(
|
|
15888
|
-
this.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(
|
|
15966
|
+
this.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and23(eq26(gbpReviewRatings.projectId, projectId), eq26(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
|
|
15889
15967
|
);
|
|
15890
15968
|
const coveredByBusinessProfile = (locationName) => businessProfileReviewsCover(locations.get(locationName)?.reviewsAccess ?? null, v4HasWorked.has(locationName));
|
|
15891
15969
|
const locationRef = (locationName) => {
|
|
@@ -15914,7 +15992,7 @@ var Notifier = class {
|
|
|
15914
15992
|
else if (row.rating === null || row.previousRating === null) setRatingState([row.id], "none");
|
|
15915
15993
|
else ratingsToSend.push(row);
|
|
15916
15994
|
}
|
|
15917
|
-
const enabled = this.db.select().from(notifications).where(
|
|
15995
|
+
const enabled = this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url);
|
|
15918
15996
|
const subscribedTo = (event) => enabled.filter((n) => (n.config.events ?? []).includes(event));
|
|
15919
15997
|
const projectRef = { name: project.name, canonicalDomain: project.canonicalDomain };
|
|
15920
15998
|
const dashboardUrl = `${this.serverUrl}/projects/${project.name}`;
|
|
@@ -15968,13 +16046,13 @@ var Notifier = class {
|
|
|
15968
16046
|
if (!sent) all = false;
|
|
15969
16047
|
}
|
|
15970
16048
|
if (all) {
|
|
15971
|
-
this.db.update(gbpReviews).set({ alertState: "sent", alertStateAt: nowIso }).where(
|
|
15972
|
-
|
|
15973
|
-
|
|
15974
|
-
|
|
15975
|
-
review.alertStateAt === null ? isNull5(gbpReviews.alertStateAt) :
|
|
15976
|
-
review.comment === null ? isNull5(gbpReviews.comment) :
|
|
15977
|
-
review.starRating === null ? isNull5(gbpReviews.starRating) :
|
|
16049
|
+
this.db.update(gbpReviews).set({ alertState: "sent", alertStateAt: nowIso }).where(and23(
|
|
16050
|
+
eq26(gbpReviews.id, review.id),
|
|
16051
|
+
eq26(gbpReviews.alertState, "pending"),
|
|
16052
|
+
eq26(gbpReviews.updateTime, review.updateTime),
|
|
16053
|
+
review.alertStateAt === null ? isNull5(gbpReviews.alertStateAt) : eq26(gbpReviews.alertStateAt, review.alertStateAt),
|
|
16054
|
+
review.comment === null ? isNull5(gbpReviews.comment) : eq26(gbpReviews.comment, review.comment),
|
|
16055
|
+
review.starRating === null ? isNull5(gbpReviews.starRating) : eq26(gbpReviews.starRating, review.starRating)
|
|
15978
16056
|
)).run();
|
|
15979
16057
|
}
|
|
15980
16058
|
}
|
|
@@ -16015,7 +16093,7 @@ var Notifier = class {
|
|
|
16015
16093
|
return { reviews: reviewsToSend.length, ratingDrops: ratingsToSend.length, delivered };
|
|
16016
16094
|
}
|
|
16017
16095
|
computeTransitions(runId, projectId) {
|
|
16018
|
-
const thisRun = this.db.select().from(runs).where(
|
|
16096
|
+
const thisRun = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
|
|
16019
16097
|
if (!thisRun) return [];
|
|
16020
16098
|
const completeness = measurementRunCompleteness(this.db, runId);
|
|
16021
16099
|
if (completeness.planned && !completeness.complete) {
|
|
@@ -16026,10 +16104,10 @@ var Notifier = class {
|
|
|
16026
16104
|
});
|
|
16027
16105
|
return [];
|
|
16028
16106
|
}
|
|
16029
|
-
const groupSiblings = this.db.select().from(runs).where(
|
|
16030
|
-
|
|
16031
|
-
|
|
16032
|
-
|
|
16107
|
+
const groupSiblings = this.db.select().from(runs).where(and23(
|
|
16108
|
+
eq26(runs.projectId, projectId),
|
|
16109
|
+
eq26(runs.kind, thisRun.kind),
|
|
16110
|
+
eq26(runs.createdAt, thisRun.createdAt),
|
|
16033
16111
|
notProbeRun()
|
|
16034
16112
|
)).all();
|
|
16035
16113
|
const stillPending = groupSiblings.some((r) => r.status === "queued" || r.status === "running");
|
|
@@ -16046,20 +16124,20 @@ var Notifier = class {
|
|
|
16046
16124
|
return candidate.id > best.id ? candidate : best;
|
|
16047
16125
|
});
|
|
16048
16126
|
if (winner.id !== runId) return [];
|
|
16049
|
-
const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(
|
|
16127
|
+
const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq26(projects.id, projectId)).get();
|
|
16050
16128
|
const locationCount = Math.max(
|
|
16051
16129
|
1,
|
|
16052
16130
|
(projectLocations?.locations ?? []).length
|
|
16053
16131
|
);
|
|
16054
16132
|
const RECENT_FETCH_LIMIT = Math.max(8, locationCount * 4);
|
|
16055
16133
|
const recentRuns = this.db.select().from(runs).where(
|
|
16056
|
-
|
|
16057
|
-
|
|
16058
|
-
|
|
16059
|
-
or2(
|
|
16134
|
+
and23(
|
|
16135
|
+
eq26(runs.projectId, projectId),
|
|
16136
|
+
eq26(runs.kind, thisRun.kind),
|
|
16137
|
+
or2(eq26(runs.status, "completed"), eq26(runs.status, "partial")),
|
|
16060
16138
|
notProbeRun()
|
|
16061
16139
|
)
|
|
16062
|
-
).orderBy(
|
|
16140
|
+
).orderBy(desc10(runs.createdAt), desc10(runs.id)).limit(RECENT_FETCH_LIMIT).all();
|
|
16063
16141
|
const groups = groupRunsByCreatedAt(recentRuns);
|
|
16064
16142
|
const currentGroupIdx = groups.findIndex((g) => g[0]?.createdAt === thisRun.createdAt);
|
|
16065
16143
|
if (currentGroupIdx < 0) return [];
|
|
@@ -16074,7 +16152,7 @@ var Notifier = class {
|
|
|
16074
16152
|
provider: querySnapshots.provider,
|
|
16075
16153
|
location: querySnapshots.location,
|
|
16076
16154
|
citationState: querySnapshots.citationState
|
|
16077
|
-
}).from(querySnapshots).leftJoin(queries,
|
|
16155
|
+
}).from(querySnapshots).leftJoin(queries, eq26(querySnapshots.queryId, queries.id)).where(inArray12(querySnapshots.runId, currentRunIds)).all();
|
|
16078
16156
|
const previousSnapshots = this.db.select({
|
|
16079
16157
|
queryId: querySnapshots.queryId,
|
|
16080
16158
|
provider: querySnapshots.provider,
|
|
@@ -16125,12 +16203,12 @@ var Notifier = class {
|
|
|
16125
16203
|
recordUnrecognizedAgentDecisions(this.db, projectId, baseline, UnrecognizedAgentNotifyStates.baseline, now);
|
|
16126
16204
|
if (due.length === 0) continue;
|
|
16127
16205
|
const names = due.map((agent) => agent.name);
|
|
16128
|
-
const subscribed = this.db.select().from(notifications).where(
|
|
16206
|
+
const subscribed = this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url && n.config.events.includes("traffic.agent.discovered"));
|
|
16129
16207
|
if (subscribed.length === 0) {
|
|
16130
16208
|
recordUnrecognizedAgentDecisions(this.db, projectId, names, UnrecognizedAgentNotifyStates.skipped, now);
|
|
16131
16209
|
continue;
|
|
16132
16210
|
}
|
|
16133
|
-
const project = this.db.select().from(projects).where(
|
|
16211
|
+
const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
16134
16212
|
if (!project) continue;
|
|
16135
16213
|
const payload = {
|
|
16136
16214
|
source: "canonry",
|
|
@@ -16153,6 +16231,73 @@ var Notifier = class {
|
|
|
16153
16231
|
}
|
|
16154
16232
|
return sentNames;
|
|
16155
16233
|
}
|
|
16234
|
+
/**
|
|
16235
|
+
* After a site audit, send `site-health.issue.new` to the webhooks that list
|
|
16236
|
+
* it when the scan found a critical page problem on a page where it was not
|
|
16237
|
+
* reported before.
|
|
16238
|
+
*
|
|
16239
|
+
* What was reported lives in `site_health_notify_state`. It changes when a
|
|
16240
|
+
* subscriber accepts an alert, so a refused alert is owed again after the
|
|
16241
|
+
* next audit, and when a complete scan no longer finds a problem, so the
|
|
16242
|
+
* problem is reported again if it comes back. A partial scan may have missed
|
|
16243
|
+
* pages, so it never clears anything. Nothing is recorded while no webhook
|
|
16244
|
+
* subscribes: a new subscriber hears about every current problem once.
|
|
16245
|
+
*
|
|
16246
|
+
* Returns the event sent, or null when nothing was sent, so "stayed quiet" is
|
|
16247
|
+
* observable in tests.
|
|
16248
|
+
*/
|
|
16249
|
+
async onSiteAuditCompleted(runId, projectId) {
|
|
16250
|
+
const run = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
|
|
16251
|
+
if (!run || run.kind !== RunKinds["site-audit"]) return null;
|
|
16252
|
+
if (run.status !== RunStatuses.completed && run.status !== RunStatuses.partial) return null;
|
|
16253
|
+
const subscribed = this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url && n.config.events.includes("site-health.issue.new"));
|
|
16254
|
+
if (subscribed.length === 0) return null;
|
|
16255
|
+
const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
16256
|
+
if (!project) return null;
|
|
16257
|
+
const scan = readSiteHealthScan(this.db, projectId, runId);
|
|
16258
|
+
if (!scan) return null;
|
|
16259
|
+
const state = readSiteHealthNotifyState(this.db, projectId);
|
|
16260
|
+
const reported = new Set(state?.reportedKeys ?? []);
|
|
16261
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16262
|
+
const issues = newSiteHealthIssues(scan, reported);
|
|
16263
|
+
if (issues.length === 0) {
|
|
16264
|
+
if (state && scan.complete) {
|
|
16265
|
+
writeSiteHealthNotifyState(this.db, {
|
|
16266
|
+
projectId,
|
|
16267
|
+
runId,
|
|
16268
|
+
reportedKeys: settledSiteHealthKeys(scan, reported),
|
|
16269
|
+
updatedAt: now,
|
|
16270
|
+
notifiedAt: state.notifiedAt
|
|
16271
|
+
});
|
|
16272
|
+
}
|
|
16273
|
+
return null;
|
|
16274
|
+
}
|
|
16275
|
+
const payload = {
|
|
16276
|
+
source: "canonry",
|
|
16277
|
+
event: "site-health.issue.new",
|
|
16278
|
+
project: { name: project.name, canonicalDomain: project.canonicalDomain },
|
|
16279
|
+
run: { id: run.id, status: run.status, finishedAt: run.finishedAt },
|
|
16280
|
+
scanComplete: scan.complete,
|
|
16281
|
+
issues,
|
|
16282
|
+
dashboardUrl: `${this.serverUrl}/projects/${project.name}/technical-aeo`
|
|
16283
|
+
};
|
|
16284
|
+
let accepted = false;
|
|
16285
|
+
for (const notif of subscribed) {
|
|
16286
|
+
if (await this.sendWebhook(notif.config.url, payload, notif.id, projectId, notif.webhookSecret ?? null)) accepted = true;
|
|
16287
|
+
}
|
|
16288
|
+
if (!accepted) {
|
|
16289
|
+
log24.info("site-health.alert-owed", { runId, projectId, issues: issues.length });
|
|
16290
|
+
return null;
|
|
16291
|
+
}
|
|
16292
|
+
writeSiteHealthNotifyState(this.db, {
|
|
16293
|
+
projectId,
|
|
16294
|
+
runId,
|
|
16295
|
+
reportedKeys: settledSiteHealthKeys(scan, reported),
|
|
16296
|
+
updatedAt: now,
|
|
16297
|
+
notifiedAt: now
|
|
16298
|
+
});
|
|
16299
|
+
return "site-health.issue.new";
|
|
16300
|
+
}
|
|
16156
16301
|
async sendWebhook(url, payload, notificationId, projectId, webhookSecret) {
|
|
16157
16302
|
const elapsed = startOutcomeTimer();
|
|
16158
16303
|
const destination = resolveDestination(url);
|
|
@@ -16220,7 +16365,7 @@ var Notifier = class {
|
|
|
16220
16365
|
}
|
|
16221
16366
|
logDelivery(projectId, notificationId, event, status, error) {
|
|
16222
16367
|
this.db.insert(auditLog).values({
|
|
16223
|
-
id:
|
|
16368
|
+
id: crypto23.randomUUID(),
|
|
16224
16369
|
projectId,
|
|
16225
16370
|
actor: "scheduler",
|
|
16226
16371
|
action: `notification.${status}`,
|
|
@@ -16233,7 +16378,7 @@ var Notifier = class {
|
|
|
16233
16378
|
};
|
|
16234
16379
|
|
|
16235
16380
|
// src/run-coordinator.ts
|
|
16236
|
-
import { eq as
|
|
16381
|
+
import { eq as eq27 } from "drizzle-orm";
|
|
16237
16382
|
var log25 = createLogger("RunCoordinator");
|
|
16238
16383
|
function reportInsightsGenerated(runTrigger, outcome) {
|
|
16239
16384
|
trackFeatureCompleted({
|
|
@@ -16267,7 +16412,7 @@ var RunCoordinator = class {
|
|
|
16267
16412
|
*/
|
|
16268
16413
|
onAnswersRecorded;
|
|
16269
16414
|
async onRunCompleted(runId, projectId, opts) {
|
|
16270
|
-
const runRow = this.db.select().from(runs).where(
|
|
16415
|
+
const runRow = this.db.select().from(runs).where(eq27(runs.id, runId)).get();
|
|
16271
16416
|
const kind = runRow?.kind ?? RunKinds["answer-visibility"];
|
|
16272
16417
|
if (runRow?.trigger === RunTriggers.probe) {
|
|
16273
16418
|
log25.info("probe.skip-side-effects", { runId, projectId, kind });
|
|
@@ -16361,6 +16506,13 @@ var RunCoordinator = class {
|
|
|
16361
16506
|
} catch (err) {
|
|
16362
16507
|
log25.error("notifier.failed", { runId, error: describeError(err) });
|
|
16363
16508
|
}
|
|
16509
|
+
if (kind === RunKinds["site-audit"]) {
|
|
16510
|
+
try {
|
|
16511
|
+
await this.notifier.onSiteAuditCompleted(runId, projectId);
|
|
16512
|
+
} catch (err) {
|
|
16513
|
+
log25.error("site-health-webhook.failed", { runId, error: describeError(err) });
|
|
16514
|
+
}
|
|
16515
|
+
}
|
|
16364
16516
|
if (this.onAeroEvent) {
|
|
16365
16517
|
try {
|
|
16366
16518
|
const ctx = kind === RunKinds["aeo-discover-probe"] ? this.buildDiscoveryAeroContext(runId, projectId, runRow?.status === "failed" ? "failed" : "completed", runRow?.error ?? null) : {
|
|
@@ -16387,7 +16539,7 @@ var RunCoordinator = class {
|
|
|
16387
16539
|
* so the Aero queue is never starved of a follow-up.
|
|
16388
16540
|
*/
|
|
16389
16541
|
buildDiscoveryAeroContext(runId, projectId, status, error) {
|
|
16390
|
-
const session = this.db.select().from(discoverySessions).where(
|
|
16542
|
+
const session = this.db.select().from(discoverySessions).where(eq27(discoverySessions.runId, runId)).get();
|
|
16391
16543
|
const competitorMap = session ? session.competitorMap : [];
|
|
16392
16544
|
return {
|
|
16393
16545
|
kind: RunKinds["aeo-discover-probe"],
|
|
@@ -16429,8 +16581,8 @@ function analysisResultFromInsights(insights2) {
|
|
|
16429
16581
|
}
|
|
16430
16582
|
|
|
16431
16583
|
// src/agent/session-registry.ts
|
|
16432
|
-
import
|
|
16433
|
-
import { eq as
|
|
16584
|
+
import crypto25 from "crypto";
|
|
16585
|
+
import { eq as eq30 } from "drizzle-orm";
|
|
16434
16586
|
|
|
16435
16587
|
// src/agent/remote-mcp.ts
|
|
16436
16588
|
import { Type } from "@earendil-works/pi-ai";
|
|
@@ -16534,8 +16686,8 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
16534
16686
|
}
|
|
16535
16687
|
|
|
16536
16688
|
// src/agent/memory-store.ts
|
|
16537
|
-
import
|
|
16538
|
-
import { and as
|
|
16689
|
+
import crypto24 from "crypto";
|
|
16690
|
+
import { and as and24, desc as desc11, eq as eq28, like, sql as sql12, or as or3 } from "drizzle-orm";
|
|
16539
16691
|
var COMPACTION_KEY_PREFIX = "compaction:";
|
|
16540
16692
|
var COMPACTION_NOTES_PER_SESSION = 3;
|
|
16541
16693
|
function rowToDto(row) {
|
|
@@ -16549,7 +16701,7 @@ function rowToDto(row) {
|
|
|
16549
16701
|
};
|
|
16550
16702
|
}
|
|
16551
16703
|
function listMemoryEntries(db, projectId, opts = {}) {
|
|
16552
|
-
const query = db.select().from(agentMemory).where(
|
|
16704
|
+
const query = db.select().from(agentMemory).where(eq28(agentMemory.projectId, projectId)).orderBy(desc11(agentMemory.updatedAt));
|
|
16553
16705
|
const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
|
|
16554
16706
|
return rows.map(rowToDto);
|
|
16555
16707
|
}
|
|
@@ -16563,7 +16715,7 @@ function upsertMemoryEntry(db, args) {
|
|
|
16563
16715
|
throw new Error(`memory key prefix "${COMPACTION_KEY_PREFIX}" is reserved for compaction notes`);
|
|
16564
16716
|
}
|
|
16565
16717
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16566
|
-
const id =
|
|
16718
|
+
const id = crypto24.randomUUID();
|
|
16567
16719
|
db.insert(agentMemory).values({
|
|
16568
16720
|
id,
|
|
16569
16721
|
projectId: args.projectId,
|
|
@@ -16580,22 +16732,22 @@ function upsertMemoryEntry(db, args) {
|
|
|
16580
16732
|
updatedAt: now
|
|
16581
16733
|
}
|
|
16582
16734
|
}).run();
|
|
16583
|
-
const row = db.select().from(agentMemory).where(
|
|
16735
|
+
const row = db.select().from(agentMemory).where(and24(eq28(agentMemory.projectId, args.projectId), eq28(agentMemory.key, args.key))).get();
|
|
16584
16736
|
if (!row) throw new Error("memory upsert produced no row");
|
|
16585
16737
|
return rowToDto(row);
|
|
16586
16738
|
}
|
|
16587
16739
|
function deleteMemoryEntry(db, projectId, key) {
|
|
16588
|
-
const result = db.delete(agentMemory).where(
|
|
16740
|
+
const result = db.delete(agentMemory).where(and24(eq28(agentMemory.projectId, projectId), eq28(agentMemory.key, key))).run();
|
|
16589
16741
|
const changes = result.changes ?? 0;
|
|
16590
16742
|
return changes > 0;
|
|
16591
16743
|
}
|
|
16592
16744
|
function loadRecentForHydrate(db, projectId, limit, sessionId) {
|
|
16593
16745
|
if (sessionId === void 0) return listMemoryEntries(db, projectId, { limit });
|
|
16594
16746
|
const sessionPrefix = `compaction:${sessionId ?? ""}:`;
|
|
16595
|
-
return db.select().from(agentMemory).where(
|
|
16596
|
-
|
|
16747
|
+
return db.select().from(agentMemory).where(and24(
|
|
16748
|
+
eq28(agentMemory.projectId, projectId),
|
|
16597
16749
|
or3(sql12`substr(${agentMemory.key}, 1, ${COMPACTION_KEY_PREFIX.length}) <> ${COMPACTION_KEY_PREFIX}`, sessionId ? sql12`substr(${agentMemory.key}, 1, ${sessionPrefix.length}) = ${sessionPrefix}` : void 0)
|
|
16598
|
-
)).orderBy(
|
|
16750
|
+
)).orderBy(desc11(agentMemory.updatedAt)).limit(limit).all().map(rowToDto);
|
|
16599
16751
|
}
|
|
16600
16752
|
function writeCompactionNote(db, args) {
|
|
16601
16753
|
if (Buffer.byteLength(args.summary, "utf8") > AGENT_MEMORY_VALUE_MAX_BYTES) {
|
|
@@ -16605,7 +16757,7 @@ function writeCompactionNote(db, args) {
|
|
|
16605
16757
|
}
|
|
16606
16758
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16607
16759
|
const key = `${COMPACTION_KEY_PREFIX}${args.sessionId}:${now}`;
|
|
16608
|
-
const id =
|
|
16760
|
+
const id = crypto24.randomUUID();
|
|
16609
16761
|
let inserted;
|
|
16610
16762
|
db.transaction((tx) => {
|
|
16611
16763
|
tx.insert(agentMemory).values({
|
|
@@ -16619,16 +16771,16 @@ function writeCompactionNote(db, args) {
|
|
|
16619
16771
|
}).run();
|
|
16620
16772
|
const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
|
|
16621
16773
|
const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
|
|
16622
|
-
|
|
16623
|
-
|
|
16774
|
+
and24(
|
|
16775
|
+
eq28(agentMemory.projectId, args.projectId),
|
|
16624
16776
|
like(agentMemory.key, `${sessionPrefix}%`)
|
|
16625
16777
|
)
|
|
16626
|
-
).orderBy(
|
|
16778
|
+
).orderBy(desc11(agentMemory.updatedAt)).all();
|
|
16627
16779
|
const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
|
|
16628
16780
|
if (stale.length > 0) {
|
|
16629
16781
|
tx.delete(agentMemory).where(sql12`${agentMemory.id} IN (${sql12.join(stale.map((s) => sql12`${s}`), sql12`, `)})`).run();
|
|
16630
16782
|
}
|
|
16631
|
-
const row = tx.select().from(agentMemory).where(
|
|
16783
|
+
const row = tx.select().from(agentMemory).where(and24(eq28(agentMemory.projectId, args.projectId), eq28(agentMemory.key, key))).get();
|
|
16632
16784
|
if (row) inserted = rowToDto(row);
|
|
16633
16785
|
});
|
|
16634
16786
|
if (!inserted) throw new Error("compaction note write produced no row");
|
|
@@ -16702,7 +16854,7 @@ function trackAeroTurn(agent, turn) {
|
|
|
16702
16854
|
}
|
|
16703
16855
|
|
|
16704
16856
|
// src/agent/project-shape.ts
|
|
16705
|
-
import { and as
|
|
16857
|
+
import { and as and25, count as count5, eq as eq29 } from "drizzle-orm";
|
|
16706
16858
|
var ADVANCED_TOOLS = [
|
|
16707
16859
|
"canonry_measurement_portfolio_summary",
|
|
16708
16860
|
"canonry_measurement_overview",
|
|
@@ -16729,10 +16881,10 @@ var ADVANCED_ROUTES = [
|
|
|
16729
16881
|
];
|
|
16730
16882
|
function aeroProjectShape(db, projectId) {
|
|
16731
16883
|
try {
|
|
16732
|
-
const pointer = db.select().from(measurementPlans).where(
|
|
16733
|
-
const version = pointer ? db.select().from(measurementPlanVersions).where(
|
|
16734
|
-
|
|
16735
|
-
|
|
16884
|
+
const pointer = db.select().from(measurementPlans).where(eq29(measurementPlans.projectId, projectId)).get();
|
|
16885
|
+
const version = pointer ? db.select().from(measurementPlanVersions).where(and25(
|
|
16886
|
+
eq29(measurementPlanVersions.projectId, projectId),
|
|
16887
|
+
eq29(measurementPlanVersions.id, pointer.activeVersionId)
|
|
16736
16888
|
)).get() : void 0;
|
|
16737
16889
|
if (version) {
|
|
16738
16890
|
const plan = JSON.parse(version.canonicalJson);
|
|
@@ -16759,7 +16911,7 @@ canonry_visibility_report describes the whole project, not one Property. canonry
|
|
|
16759
16911
|
pinned: ADVANCED_TOOLS
|
|
16760
16912
|
};
|
|
16761
16913
|
}
|
|
16762
|
-
const tracked = db.select({ total: count5() }).from(queries).where(
|
|
16914
|
+
const tracked = db.select({ total: count5() }).from(queries).where(eq29(queries.projectId, projectId)).get()?.total ?? 0;
|
|
16763
16915
|
return {
|
|
16764
16916
|
prompt: `
|
|
16765
16917
|
|
|
@@ -17018,7 +17170,7 @@ var SessionRegistry = class {
|
|
|
17018
17170
|
modelProvider: effectiveProvider,
|
|
17019
17171
|
modelId: effectiveModelId,
|
|
17020
17172
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17021
|
-
}).where(
|
|
17173
|
+
}).where(eq30(agentSessions.projectId, projectId)).run();
|
|
17022
17174
|
}
|
|
17023
17175
|
const agent2 = createAeroSession({
|
|
17024
17176
|
projectName,
|
|
@@ -17047,7 +17199,7 @@ var SessionRegistry = class {
|
|
|
17047
17199
|
return agent2;
|
|
17048
17200
|
}
|
|
17049
17201
|
const { provider, modelId } = resolveSessionProviderAndModel(this.opts.config, preferences);
|
|
17050
|
-
const sessionId =
|
|
17202
|
+
const sessionId = crypto25.randomUUID();
|
|
17051
17203
|
const agent = createAeroSession({
|
|
17052
17204
|
projectName,
|
|
17053
17205
|
client: this.opts.client,
|
|
@@ -17337,7 +17489,7 @@ ${lines.join("\n")}
|
|
|
17337
17489
|
modelProvider: nextProvider,
|
|
17338
17490
|
modelId: nextModelId,
|
|
17339
17491
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17340
|
-
}).where(
|
|
17492
|
+
}).where(eq30(agentSessions.projectId, projectId)).run();
|
|
17341
17493
|
}
|
|
17342
17494
|
/** Snapshot pending work without consuming it before an atomic conversation switch. */
|
|
17343
17495
|
prepareConversationChange(projectName) {
|
|
@@ -17545,7 +17697,7 @@ ${lines.join("\n")}
|
|
|
17545
17697
|
providerAccountKey(provider, apiKey) {
|
|
17546
17698
|
const canonical = coerceAgentProvider(provider);
|
|
17547
17699
|
const vendor = canonical ? getAgentProvider(canonical).piAiProvider : provider;
|
|
17548
|
-
return
|
|
17700
|
+
return crypto25.createHash("sha256").update(JSON.stringify([vendor, apiKey ?? ""])).digest("hex");
|
|
17549
17701
|
}
|
|
17550
17702
|
providerCoolingDown(projectName) {
|
|
17551
17703
|
for (const [key, cooldown] of this.providerCooldowns) {
|
|
@@ -17562,18 +17714,18 @@ ${lines.join("\n")}
|
|
|
17562
17714
|
return id;
|
|
17563
17715
|
}
|
|
17564
17716
|
tryResolveProjectId(projectName) {
|
|
17565
|
-
const row = this.opts.db.select({ id: projects.id }).from(projects).where(
|
|
17717
|
+
const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq30(projects.name, projectName)).get();
|
|
17566
17718
|
return row?.id;
|
|
17567
17719
|
}
|
|
17568
17720
|
loadRow(projectId) {
|
|
17569
|
-
const row = this.opts.db.select().from(agentSessions).where(
|
|
17721
|
+
const row = this.opts.db.select().from(agentSessions).where(eq30(agentSessions.projectId, projectId)).get();
|
|
17570
17722
|
return row ?? null;
|
|
17571
17723
|
}
|
|
17572
17724
|
insertRow(params) {
|
|
17573
17725
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
17574
17726
|
const modelProvider = params.provider ?? params.modelProvider ?? AgentProviderIds.claude;
|
|
17575
17727
|
this.opts.db.insert(agentSessions).values({
|
|
17576
|
-
id: params.id ??
|
|
17728
|
+
id: params.id ?? crypto25.randomUUID(),
|
|
17577
17729
|
projectId: params.projectId,
|
|
17578
17730
|
systemPrompt: params.systemPrompt,
|
|
17579
17731
|
modelProvider,
|
|
@@ -17586,7 +17738,7 @@ ${lines.join("\n")}
|
|
|
17586
17738
|
}
|
|
17587
17739
|
updateRow(projectId, patch) {
|
|
17588
17740
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
17589
|
-
this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(
|
|
17741
|
+
this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq30(agentSessions.projectId, projectId)).run();
|
|
17590
17742
|
}
|
|
17591
17743
|
/**
|
|
17592
17744
|
* Store a refreshed prompt snapshot without touching `updatedAt`, which the
|
|
@@ -17595,13 +17747,13 @@ ${lines.join("\n")}
|
|
|
17595
17747
|
* recently used right after an upgrade.
|
|
17596
17748
|
*/
|
|
17597
17749
|
persistPromptSnapshot(projectId, systemPrompt) {
|
|
17598
|
-
this.opts.db.update(agentSessions).set({ systemPrompt }).where(
|
|
17750
|
+
this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq30(agentSessions.projectId, projectId)).run();
|
|
17599
17751
|
}
|
|
17600
17752
|
};
|
|
17601
17753
|
|
|
17602
17754
|
// src/agent/viewer-sessions.ts
|
|
17603
|
-
import
|
|
17604
|
-
import { and as
|
|
17755
|
+
import crypto26 from "crypto";
|
|
17756
|
+
import { and as and26, eq as eq31, isNotNull as isNotNull4, like as like2 } from "drizzle-orm";
|
|
17605
17757
|
var AERO_VIEWER_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
|
|
17606
17758
|
CanonryMcpToolNames.canonry_ads_account,
|
|
17607
17759
|
CanonryMcpToolNames.canonry_ads_geo_search,
|
|
@@ -17654,8 +17806,8 @@ function trimViewerTranscript(messages, max2 = MAX_VIEWER_MESSAGES) {
|
|
|
17654
17806
|
}
|
|
17655
17807
|
var VIEWER_IDLE_MS = 12 * 60 * 60 * 1e3;
|
|
17656
17808
|
function mintViewerAeroKey(db, userId, now = /* @__PURE__ */ new Date()) {
|
|
17657
|
-
const raw = `cnry_${
|
|
17658
|
-
const id =
|
|
17809
|
+
const raw = `cnry_${crypto26.randomBytes(24).toString("hex")}`;
|
|
17810
|
+
const id = crypto26.randomUUID();
|
|
17659
17811
|
db.insert(apiKeys).values({
|
|
17660
17812
|
id,
|
|
17661
17813
|
name: `${VIEWER_AERO_KEY_PREFIX}${userId}`,
|
|
@@ -17778,12 +17930,12 @@ var ViewerAeroSessions = class {
|
|
|
17778
17930
|
*/
|
|
17779
17931
|
removeKey(id) {
|
|
17780
17932
|
try {
|
|
17781
|
-
this.opts.db.delete(apiKeys).where(
|
|
17933
|
+
this.opts.db.delete(apiKeys).where(eq31(apiKeys.id, id)).run();
|
|
17782
17934
|
} catch {
|
|
17783
17935
|
try {
|
|
17784
17936
|
this.opts.db.transaction((tx) => {
|
|
17785
|
-
tx.delete(dashboardSessions).where(
|
|
17786
|
-
tx.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(
|
|
17937
|
+
tx.delete(dashboardSessions).where(eq31(dashboardSessions.apiKeyId, id)).run();
|
|
17938
|
+
tx.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(eq31(apiKeys.id, id)).run();
|
|
17787
17939
|
});
|
|
17788
17940
|
} catch {
|
|
17789
17941
|
}
|
|
@@ -17796,7 +17948,7 @@ var ViewerAeroSessions = class {
|
|
|
17796
17948
|
*/
|
|
17797
17949
|
revokeOrphanedKeys() {
|
|
17798
17950
|
try {
|
|
17799
|
-
this.opts.db.delete(apiKeys).where(
|
|
17951
|
+
this.opts.db.delete(apiKeys).where(and26(like2(apiKeys.name, `${VIEWER_AERO_KEY_PREFIX}%`), isNotNull4(apiKeys.delegatedUserId))).run();
|
|
17800
17952
|
} catch {
|
|
17801
17953
|
}
|
|
17802
17954
|
}
|
|
@@ -17811,9 +17963,9 @@ var ViewerAeroSessions = class {
|
|
|
17811
17963
|
};
|
|
17812
17964
|
|
|
17813
17965
|
// src/mcp-http.ts
|
|
17814
|
-
import
|
|
17966
|
+
import crypto27 from "crypto";
|
|
17815
17967
|
import { isIP } from "net";
|
|
17816
|
-
import { and as
|
|
17968
|
+
import { and as and27, eq as eq32, isNull as isNull6 } from "drizzle-orm";
|
|
17817
17969
|
import { NodeStreamableHTTPServerTransport, toNodeHandler, toWebRequest } from "@modelcontextprotocol/node";
|
|
17818
17970
|
import { McpServer, createMcpHandler, isInitializeRequest, isLegacyRequest } from "@modelcontextprotocol/server";
|
|
17819
17971
|
var SESSION_IDLE_MS = 10 * 60 * 1e3;
|
|
@@ -17822,8 +17974,8 @@ var MAX_CLEANUP_RETRIES_PER_SWEEP = 8;
|
|
|
17822
17974
|
var MAX_PENDING_KEY_RETIREMENTS = 256;
|
|
17823
17975
|
var MAX_CLEANUP_BACKOFF_MS = 10 * 60 * 1e3;
|
|
17824
17976
|
function mintSessionKey(db, scopes, userId, oauthGrantId, createdAt) {
|
|
17825
|
-
const raw = `cnry_${
|
|
17826
|
-
const id =
|
|
17977
|
+
const raw = `cnry_${crypto27.randomBytes(24).toString("hex")}`;
|
|
17978
|
+
const id = crypto27.randomUUID();
|
|
17827
17979
|
db.insert(apiKeys).values({
|
|
17828
17980
|
id,
|
|
17829
17981
|
name: `mcp-session:${userId}`,
|
|
@@ -17919,12 +18071,12 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
17919
18071
|
try {
|
|
17920
18072
|
withoutLockWait(() => {
|
|
17921
18073
|
opts.db.transaction((tx) => {
|
|
17922
|
-
tx.delete(dashboardSessions).where(
|
|
17923
|
-
tx.update(apiKeys).set({ revokedAt: new Date(now()).toISOString() }).where(
|
|
18074
|
+
tx.delete(dashboardSessions).where(eq32(dashboardSessions.apiKeyId, id)).run();
|
|
18075
|
+
tx.update(apiKeys).set({ revokedAt: new Date(now()).toISOString() }).where(and27(eq32(apiKeys.id, id), isNull6(apiKeys.revokedAt))).run();
|
|
17924
18076
|
});
|
|
17925
18077
|
if (remove) {
|
|
17926
18078
|
try {
|
|
17927
|
-
opts.db.delete(apiKeys).where(
|
|
18079
|
+
opts.db.delete(apiKeys).where(eq32(apiKeys.id, id)).run();
|
|
17928
18080
|
} catch (error) {
|
|
17929
18081
|
scope.log.warn({ err: error, keyId: id }, "Retained revoked MCP request key after deletion failed");
|
|
17930
18082
|
}
|
|
@@ -17975,14 +18127,14 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
17975
18127
|
withoutLockWait(() => {
|
|
17976
18128
|
const cutoff = now() - SESSION_IDLE_MS;
|
|
17977
18129
|
for (const session of legacyServers) {
|
|
17978
|
-
const key = session.sessionKeyId ? opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(
|
|
18130
|
+
const key = session.sessionKeyId ? opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq32(apiKeys.id, session.sessionKeyId)).get() : void 0;
|
|
17979
18131
|
if (session.lastSeenAt < cutoff || session.expiresAt !== void 0 && session.expiresAt <= now() || session.sessionKeyId && (!key || key.revokedAt)) {
|
|
17980
18132
|
void session.close().catch((error) => scope.log.error({ err: error }, "Failed to close expired MCP session"));
|
|
17981
18133
|
}
|
|
17982
18134
|
}
|
|
17983
18135
|
for (const exchange of modernExchanges) {
|
|
17984
18136
|
if (!exchange.sessionKey) continue;
|
|
17985
|
-
const key = opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(
|
|
18137
|
+
const key = opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq32(apiKeys.id, exchange.sessionKey.id)).get();
|
|
17986
18138
|
if (!key || key.revokedAt || exchange.sessionKey.expiresAt <= now()) {
|
|
17987
18139
|
void exchange.close().catch((error) => scope.log.error({ err: error }, "Failed to close expired MCP request"));
|
|
17988
18140
|
}
|
|
@@ -18037,7 +18189,7 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
18037
18189
|
clientName: `canonry-mcp/${PACKAGE_VERSION}`,
|
|
18038
18190
|
surface: "mcp-http",
|
|
18039
18191
|
// Correlation only: this is never accepted as caller identity or authority.
|
|
18040
|
-
...modern ? {} : { actorSession:
|
|
18192
|
+
...modern ? {} : { actorSession: crypto27.randomUUID() }
|
|
18041
18193
|
});
|
|
18042
18194
|
let server;
|
|
18043
18195
|
try {
|
|
@@ -18062,7 +18214,7 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
18062
18214
|
if (!bearer) return null;
|
|
18063
18215
|
const { server, sessionKey } = createAuthorizedServer(request, segment, bearer);
|
|
18064
18216
|
const transport = new NodeStreamableHTTPServerTransport({
|
|
18065
|
-
sessionIdGenerator: () =>
|
|
18217
|
+
sessionIdGenerator: () => crypto27.randomUUID(),
|
|
18066
18218
|
onsessioninitialized: (id) => {
|
|
18067
18219
|
if (!shuttingDown && !closing) sessions.set(id, session);
|
|
18068
18220
|
}
|
|
@@ -18232,7 +18384,7 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
18232
18384
|
}
|
|
18233
18385
|
|
|
18234
18386
|
// src/agent/agent-routes.ts
|
|
18235
|
-
import { eq as
|
|
18387
|
+
import { eq as eq33 } from "drizzle-orm";
|
|
18236
18388
|
function viewerAeroCaller(request, opts) {
|
|
18237
18389
|
if (!opts.viewerSessions) return null;
|
|
18238
18390
|
const principal = request.principal;
|
|
@@ -18272,7 +18424,7 @@ function turnAttribution(request) {
|
|
|
18272
18424
|
};
|
|
18273
18425
|
}
|
|
18274
18426
|
function resolveProject(db, name) {
|
|
18275
|
-
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(
|
|
18427
|
+
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq33(projects.name, name)).get();
|
|
18276
18428
|
if (!row) throw notFound("project", name);
|
|
18277
18429
|
return row;
|
|
18278
18430
|
}
|
|
@@ -18391,7 +18543,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
18391
18543
|
}
|
|
18392
18544
|
requireInstanceAdministrator(request);
|
|
18393
18545
|
const project = resolveProject(opts.db, request.params.name);
|
|
18394
|
-
const row = opts.db.select().from(agentSessions).where(
|
|
18546
|
+
const row = opts.db.select().from(agentSessions).where(eq33(agentSessions.projectId, project.id)).get();
|
|
18395
18547
|
const nextTurn = opts.sessionRegistry.nextTurnModel(project.name);
|
|
18396
18548
|
if (!row) {
|
|
18397
18549
|
return { conversationId: null, messages: [], modelProvider: null, modelId: null, nextTurn, updatedAt: null };
|
|
@@ -18433,7 +18585,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
18433
18585
|
const project = resolveProject(opts.db, request.params.name);
|
|
18434
18586
|
if (opts.sessionRegistry.isBusy(project.name)) throw agentBusy(project.name);
|
|
18435
18587
|
opts.sessionRegistry.reset(project.name);
|
|
18436
|
-
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
18588
|
+
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq33(agentSessions.projectId, project.id)).run();
|
|
18437
18589
|
return { status: "reset" };
|
|
18438
18590
|
}
|
|
18439
18591
|
);
|
|
@@ -18457,7 +18609,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
18457
18609
|
return streamViewerTurn(opts, project, viewerId, body, reply, turnAttribution(request));
|
|
18458
18610
|
}
|
|
18459
18611
|
if (body.conversationId !== void 0) {
|
|
18460
|
-
const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(
|
|
18612
|
+
const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(eq33(agentSessions.projectId, project.id)).get();
|
|
18461
18613
|
if ((current?.id ?? null) !== body.conversationId) throw validationError("The active conversation changed. Reload the conversation before sending.");
|
|
18462
18614
|
}
|
|
18463
18615
|
const promptText = body.prompt;
|
|
@@ -19417,7 +19569,7 @@ function clipText(value, length) {
|
|
|
19417
19569
|
}
|
|
19418
19570
|
|
|
19419
19571
|
// src/research-runner.ts
|
|
19420
|
-
import { and as
|
|
19572
|
+
import { and as and28, eq as eq34, inArray as inArray13, sql as sql13 } from "drizzle-orm";
|
|
19421
19573
|
var unfinishedResearchQueryStatuses = [ResearchQueryStatuses.queued, ResearchQueryStatuses.running];
|
|
19422
19574
|
function finalResearchRunStatus(completed, failed, waiting) {
|
|
19423
19575
|
if (waiting > 0) return ResearchRunStatuses["waiting-upstream"];
|
|
@@ -19425,13 +19577,13 @@ function finalResearchRunStatus(completed, failed, waiting) {
|
|
|
19425
19577
|
return completed > 0 ? ResearchRunStatuses.partial : ResearchRunStatuses.failed;
|
|
19426
19578
|
}
|
|
19427
19579
|
async function executeResearchRun(db, registry, runId, projectId, options = {}) {
|
|
19428
|
-
const run = db.select().from(researchRuns).where(
|
|
19580
|
+
const run = db.select().from(researchRuns).where(and28(eq34(researchRuns.id, runId), eq34(researchRuns.projectId, projectId))).get();
|
|
19429
19581
|
if (!run || run.status !== ResearchRunStatuses.queued) return;
|
|
19430
|
-
const project = db.select().from(projects).where(
|
|
19582
|
+
const project = db.select().from(projects).where(eq34(projects.id, projectId)).get();
|
|
19431
19583
|
if (!project) return;
|
|
19432
19584
|
const provider = registry.get(run.provider);
|
|
19433
19585
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19434
|
-
const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(
|
|
19586
|
+
const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(and28(eq34(researchRuns.id, runId), eq34(researchRuns.status, ResearchRunStatuses.queued))).run();
|
|
19435
19587
|
if (claim.changes !== 1) return;
|
|
19436
19588
|
const elapsed = startOutcomeTimer();
|
|
19437
19589
|
const requested = currentOutcomeOrigin();
|
|
@@ -19465,8 +19617,8 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
|
|
|
19465
19617
|
}
|
|
19466
19618
|
reserved = run.totalQueries;
|
|
19467
19619
|
reservation = { scope, period };
|
|
19468
|
-
const rows = db.select().from(researchRunQueries).where(
|
|
19469
|
-
const competitorRows = db.select(competitorIdentityColumns).from(competitors).where(
|
|
19620
|
+
const rows = db.select().from(researchRunQueries).where(eq34(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
|
|
19621
|
+
const competitorRows = db.select(competitorIdentityColumns).from(competitors).where(eq34(competitors.projectId, projectId)).orderBy(competitors.domain).all();
|
|
19470
19622
|
const competitorDomains = competitorRows.map((row) => row.domain);
|
|
19471
19623
|
const competitorAliases = new Map(competitorRows.map((row) => [row.domain, competitorIdentityAliases(row)]).filter(([, aliases]) => aliases.length > 0));
|
|
19472
19624
|
const domains = effectiveDomains(project);
|
|
@@ -19476,7 +19628,7 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
|
|
|
19476
19628
|
await mapWithConcurrency(rows, Math.max(1, provider.config.quotaPolicy.maxConcurrency), async (row) => {
|
|
19477
19629
|
try {
|
|
19478
19630
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
19479
|
-
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(
|
|
19631
|
+
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(and28(eq34(researchRunQueries.id, row.id), eq34(researchRunQueries.status, ResearchQueryStatuses.queued))).run();
|
|
19480
19632
|
const raw = await gate.run(async () => {
|
|
19481
19633
|
dispatched++;
|
|
19482
19634
|
return provider.adapter.executeTrackedQuery({ query: row.queryText, canonicalDomains: domains, competitorDomains, ...run.location ? { location: run.location } : {} }, config);
|
|
@@ -19507,7 +19659,7 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
|
|
|
19507
19659
|
rawResponse: raw.rawResponse,
|
|
19508
19660
|
answerEvidence: raw.evidence ?? null,
|
|
19509
19661
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19510
|
-
}).where(
|
|
19662
|
+
}).where(and28(eq34(researchRunQueries.id, row.id), eq34(researchRunQueries.status, ResearchQueryStatuses.running))).run();
|
|
19511
19663
|
if (completed.changes === 1) incrementResearchProgress(db, runId, "completedQueries");
|
|
19512
19664
|
} catch (error) {
|
|
19513
19665
|
if (!(error instanceof IncompleteAnswerError && error.resumable)) queryFailure ??= error;
|
|
@@ -19558,11 +19710,11 @@ function researchOutcomeStatus(status) {
|
|
|
19558
19710
|
}
|
|
19559
19711
|
}
|
|
19560
19712
|
function incrementResearchProgress(db, runId, column) {
|
|
19561
|
-
db.update(researchRuns).set({ [column]: sql13`${researchRuns[column]} + 1` }).where(
|
|
19713
|
+
db.update(researchRuns).set({ [column]: sql13`${researchRuns[column]} + 1` }).where(eq34(researchRuns.id, runId)).run();
|
|
19562
19714
|
}
|
|
19563
19715
|
function markResearchQueryFailed(db, runId, queryId, error) {
|
|
19564
19716
|
const message = describeError(error);
|
|
19565
|
-
const unfinished =
|
|
19717
|
+
const unfinished = and28(eq34(researchRunQueries.id, queryId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses));
|
|
19566
19718
|
if (error instanceof IncompleteAnswerError && error.resumable) {
|
|
19567
19719
|
db.update(researchRunQueries).set({
|
|
19568
19720
|
status: ResearchQueryStatuses["waiting-upstream"],
|
|
@@ -19578,10 +19730,10 @@ function markResearchQueryFailed(db, runId, queryId, error) {
|
|
|
19578
19730
|
if (failed.changes === 1) incrementResearchProgress(db, runId, "failedQueries");
|
|
19579
19731
|
}
|
|
19580
19732
|
function markUnfinishedResearchQueriesFailed(db, runId, error) {
|
|
19581
|
-
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
19733
|
+
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and28(eq34(researchRunQueries.researchRunId, runId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
|
|
19582
19734
|
}
|
|
19583
19735
|
function finalizeResearchRun(db, runId, fatalError) {
|
|
19584
|
-
const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(
|
|
19736
|
+
const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq34(researchRunQueries.researchRunId, runId)).all();
|
|
19585
19737
|
const completed = rows.filter((row) => row.status === ResearchQueryStatuses.completed).length;
|
|
19586
19738
|
const failed = rows.filter((row) => row.status === ResearchQueryStatuses.failed).length;
|
|
19587
19739
|
const waiting = rows.filter((row) => row.status === ResearchQueryStatuses["waiting-upstream"]).length;
|
|
@@ -19593,12 +19745,12 @@ function finalizeResearchRun(db, runId, fatalError) {
|
|
|
19593
19745
|
error: fatalError ?? (failed === rows.length ? "Every research query failed." : null),
|
|
19594
19746
|
// A run waiting on upstream jobs has not finished.
|
|
19595
19747
|
finishedAt: waiting > 0 ? null : (/* @__PURE__ */ new Date()).toISOString()
|
|
19596
|
-
}).where(
|
|
19748
|
+
}).where(eq34(researchRuns.id, runId)).run();
|
|
19597
19749
|
return { status, total: rows.length, completed, failed };
|
|
19598
19750
|
}
|
|
19599
19751
|
|
|
19600
19752
|
// src/google-marketing-sync.ts
|
|
19601
|
-
import { and as
|
|
19753
|
+
import { and as and29, eq as eq35, isNull as isNull7, sql as sql14 } from "drizzle-orm";
|
|
19602
19754
|
var RUNTIME_FAILURE_REASONS = {
|
|
19603
19755
|
INVALID_INPUT: OutcomeReasonCodes.VALIDATION,
|
|
19604
19756
|
NOT_CONFIGURED: OutcomeReasonCodes.NOT_CONNECTED,
|
|
@@ -19635,10 +19787,10 @@ function assertSnapshotContext(metadata, expected) {
|
|
|
19635
19787
|
}
|
|
19636
19788
|
}
|
|
19637
19789
|
function requireSyncRun(db, runId, projectId, kind) {
|
|
19638
|
-
const run = db.select().from(runs).where(
|
|
19639
|
-
|
|
19640
|
-
|
|
19641
|
-
|
|
19790
|
+
const run = db.select().from(runs).where(and29(
|
|
19791
|
+
eq35(runs.id, runId),
|
|
19792
|
+
eq35(runs.projectId, projectId),
|
|
19793
|
+
eq35(runs.kind, kind)
|
|
19642
19794
|
)).get();
|
|
19643
19795
|
if (!run) throw withOutcomeReason(new Error(`Run ${runId} is not a ${kind} run for this project.`), OutcomeReasonCodes.NOT_FOUND);
|
|
19644
19796
|
if (run.status !== RunStatuses.queued) {
|
|
@@ -19654,19 +19806,19 @@ function markFailed(db, runId, projectId, kind, expectedStatus, error) {
|
|
|
19654
19806
|
status: RunStatuses.failed,
|
|
19655
19807
|
error: serializeRunError({ message: safeErrorMessage(error) }),
|
|
19656
19808
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19657
|
-
}).where(
|
|
19658
|
-
|
|
19659
|
-
|
|
19660
|
-
|
|
19661
|
-
|
|
19809
|
+
}).where(and29(
|
|
19810
|
+
eq35(runs.id, runId),
|
|
19811
|
+
eq35(runs.projectId, projectId),
|
|
19812
|
+
eq35(runs.kind, kind),
|
|
19813
|
+
eq35(runs.status, expectedStatus)
|
|
19662
19814
|
)).run();
|
|
19663
19815
|
}
|
|
19664
19816
|
function claimSyncRun(db, runId, projectId, kind, startedAt) {
|
|
19665
|
-
const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(
|
|
19666
|
-
|
|
19667
|
-
|
|
19668
|
-
|
|
19669
|
-
|
|
19817
|
+
const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(and29(
|
|
19818
|
+
eq35(runs.id, runId),
|
|
19819
|
+
eq35(runs.projectId, projectId),
|
|
19820
|
+
eq35(runs.kind, kind),
|
|
19821
|
+
eq35(runs.status, RunStatuses.queued)
|
|
19670
19822
|
)).run();
|
|
19671
19823
|
return claim.changes === 1;
|
|
19672
19824
|
}
|
|
@@ -19676,9 +19828,9 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
|
|
|
19676
19828
|
let evidenceCollected = false;
|
|
19677
19829
|
try {
|
|
19678
19830
|
requireSyncRun(db, runId, projectId, RunKinds["google-ads-sync"]);
|
|
19679
|
-
const project = db.select().from(projects).where(
|
|
19831
|
+
const project = db.select().from(projects).where(eq35(projects.id, projectId)).get();
|
|
19680
19832
|
if (!project) throw withOutcomeReason(new Error(`Project ${projectId} was not found.`), OutcomeReasonCodes.NOT_FOUND);
|
|
19681
|
-
const connection = db.select().from(googleAdsConnections).where(
|
|
19833
|
+
const connection = db.select().from(googleAdsConnections).where(eq35(googleAdsConnections.projectId, projectId)).get();
|
|
19682
19834
|
if (!connection?.selectedCustomerId) {
|
|
19683
19835
|
throw withOutcomeReason(new Error("Google Ads needs a selected customer before it can sync."), OutcomeReasonCodes.ACCOUNT_NOT_FOUND);
|
|
19684
19836
|
}
|
|
@@ -19730,11 +19882,11 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
|
|
|
19730
19882
|
status: RunStatuses.completed,
|
|
19731
19883
|
finishedAt,
|
|
19732
19884
|
error: null
|
|
19733
|
-
}).where(
|
|
19734
|
-
|
|
19735
|
-
|
|
19736
|
-
|
|
19737
|
-
|
|
19885
|
+
}).where(and29(
|
|
19886
|
+
eq35(runs.id, runId),
|
|
19887
|
+
eq35(runs.projectId, projectId),
|
|
19888
|
+
eq35(runs.kind, RunKinds["google-ads-sync"]),
|
|
19889
|
+
eq35(runs.status, RunStatuses.running)
|
|
19738
19890
|
)).run();
|
|
19739
19891
|
if (terminalClaim.changes !== 1) {
|
|
19740
19892
|
throw withOutcomeReason(
|
|
@@ -19754,12 +19906,12 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
|
|
|
19754
19906
|
lastMetricsSnapshotAt: result.metrics?.metadata.capturedAt ?? connection.lastMetricsSnapshotAt,
|
|
19755
19907
|
lastMetricsSnapshotId: result.metrics?.metadata.id ?? connection.lastMetricsSnapshotId,
|
|
19756
19908
|
updatedAt: finishedAt
|
|
19757
|
-
}).where(
|
|
19758
|
-
|
|
19759
|
-
|
|
19760
|
-
|
|
19761
|
-
connection.selectedLoginCustomerId === null ? isNull7(googleAdsConnections.selectedLoginCustomerId) :
|
|
19762
|
-
|
|
19909
|
+
}).where(and29(
|
|
19910
|
+
eq35(googleAdsConnections.id, connection.id),
|
|
19911
|
+
eq35(googleAdsConnections.projectId, projectId),
|
|
19912
|
+
eq35(googleAdsConnections.selectedCustomerId, connection.selectedCustomerId),
|
|
19913
|
+
connection.selectedLoginCustomerId === null ? isNull7(googleAdsConnections.selectedLoginCustomerId) : eq35(googleAdsConnections.selectedLoginCustomerId, connection.selectedLoginCustomerId),
|
|
19914
|
+
eq35(googleAdsConnections.selectionGeneration, connection.selectionGeneration)
|
|
19763
19915
|
)).run();
|
|
19764
19916
|
if (connectionClaim.changes !== 1) {
|
|
19765
19917
|
throw withOutcomeReason(
|
|
@@ -19799,9 +19951,9 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
19799
19951
|
let evidenceCollected = false;
|
|
19800
19952
|
try {
|
|
19801
19953
|
requireSyncRun(db, runId, projectId, RunKinds["gtm-sync"]);
|
|
19802
|
-
const project = db.select().from(projects).where(
|
|
19954
|
+
const project = db.select().from(projects).where(eq35(projects.id, projectId)).get();
|
|
19803
19955
|
if (!project) throw withOutcomeReason(new Error(`Project ${projectId} was not found.`), OutcomeReasonCodes.NOT_FOUND);
|
|
19804
|
-
const connection = db.select().from(gtmConnections).where(
|
|
19956
|
+
const connection = db.select().from(gtmConnections).where(eq35(gtmConnections.projectId, projectId)).get();
|
|
19805
19957
|
if (!connection?.selectedAccountId || !connection.selectedContainerId) {
|
|
19806
19958
|
throw withOutcomeReason(new Error("GTM needs a selected account and container before it can sync."), OutcomeReasonCodes.ACCOUNT_NOT_FOUND);
|
|
19807
19959
|
}
|
|
@@ -19839,11 +19991,11 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
19839
19991
|
status: RunStatuses.completed,
|
|
19840
19992
|
finishedAt,
|
|
19841
19993
|
error: null
|
|
19842
|
-
}).where(
|
|
19843
|
-
|
|
19844
|
-
|
|
19845
|
-
|
|
19846
|
-
|
|
19994
|
+
}).where(and29(
|
|
19995
|
+
eq35(runs.id, runId),
|
|
19996
|
+
eq35(runs.projectId, projectId),
|
|
19997
|
+
eq35(runs.kind, RunKinds["gtm-sync"]),
|
|
19998
|
+
eq35(runs.status, RunStatuses.running)
|
|
19847
19999
|
)).run();
|
|
19848
20000
|
if (terminalClaim.changes !== 1) {
|
|
19849
20001
|
throw withOutcomeReason(
|
|
@@ -19864,13 +20016,13 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
19864
20016
|
lastSnapshotAt: snapshot.metadata.capturedAt,
|
|
19865
20017
|
lastSnapshotId: snapshot.metadata.id,
|
|
19866
20018
|
updatedAt: finishedAt
|
|
19867
|
-
}).where(
|
|
19868
|
-
|
|
19869
|
-
|
|
19870
|
-
|
|
19871
|
-
|
|
19872
|
-
connection.selectedWorkspaceId === null ? isNull7(gtmConnections.selectedWorkspaceId) :
|
|
19873
|
-
|
|
20019
|
+
}).where(and29(
|
|
20020
|
+
eq35(gtmConnections.id, connection.id),
|
|
20021
|
+
eq35(gtmConnections.projectId, projectId),
|
|
20022
|
+
eq35(gtmConnections.selectedAccountId, connection.selectedAccountId),
|
|
20023
|
+
eq35(gtmConnections.selectedContainerId, connection.selectedContainerId),
|
|
20024
|
+
connection.selectedWorkspaceId === null ? isNull7(gtmConnections.selectedWorkspaceId) : eq35(gtmConnections.selectedWorkspaceId, connection.selectedWorkspaceId),
|
|
20025
|
+
eq35(gtmConnections.selectionGeneration, connection.selectionGeneration)
|
|
19874
20026
|
)).run();
|
|
19875
20027
|
if (connectionClaim.changes !== 1) {
|
|
19876
20028
|
throw withOutcomeReason(
|
|
@@ -20073,8 +20225,8 @@ var DASHBOARD_SCRYPT_KEYLEN = 64;
|
|
|
20073
20225
|
var DASHBOARD_SCRYPT_COST = 1 << 15;
|
|
20074
20226
|
var DASHBOARD_SCRYPT_MAXMEM = 64 * 1024 * 1024;
|
|
20075
20227
|
function hashDashboardPassword(password) {
|
|
20076
|
-
const salt =
|
|
20077
|
-
const derived =
|
|
20228
|
+
const salt = crypto28.randomBytes(16);
|
|
20229
|
+
const derived = crypto28.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
|
|
20078
20230
|
N: DASHBOARD_SCRYPT_COST,
|
|
20079
20231
|
maxmem: DASHBOARD_SCRYPT_MAXMEM
|
|
20080
20232
|
});
|
|
@@ -20095,14 +20247,14 @@ function verifyDashboardPassword(password, storedHash) {
|
|
|
20095
20247
|
} catch {
|
|
20096
20248
|
return { ok: false, needsRehash: false };
|
|
20097
20249
|
}
|
|
20098
|
-
const derived =
|
|
20250
|
+
const derived = crypto28.scryptSync(password, salt, expected.length, {
|
|
20099
20251
|
N: DASHBOARD_SCRYPT_COST,
|
|
20100
20252
|
maxmem: DASHBOARD_SCRYPT_MAXMEM
|
|
20101
20253
|
});
|
|
20102
20254
|
if (derived.length !== expected.length)
|
|
20103
20255
|
return { ok: false, needsRehash: false };
|
|
20104
20256
|
return {
|
|
20105
|
-
ok:
|
|
20257
|
+
ok: crypto28.timingSafeEqual(derived, expected),
|
|
20106
20258
|
needsRehash: false
|
|
20107
20259
|
};
|
|
20108
20260
|
}
|
|
@@ -20111,7 +20263,7 @@ function verifyDashboardPassword(password, storedHash) {
|
|
|
20111
20263
|
const expected = Buffer.from(storedHash, "hex");
|
|
20112
20264
|
if (candidate.length !== expected.length)
|
|
20113
20265
|
return { ok: false, needsRehash: false };
|
|
20114
|
-
const ok =
|
|
20266
|
+
const ok = crypto28.timingSafeEqual(candidate, expected);
|
|
20115
20267
|
return { ok, needsRehash: ok };
|
|
20116
20268
|
}
|
|
20117
20269
|
return { ok: false, needsRehash: false };
|
|
@@ -20278,7 +20430,7 @@ function cloneGtmConfig(config) {
|
|
|
20278
20430
|
function createGoogleMarketingConfigCredentialStore(input) {
|
|
20279
20431
|
const persistConfigPatch = input.saveConfigPatch ?? saveConfigPatch;
|
|
20280
20432
|
const env = input.env ?? process.env;
|
|
20281
|
-
const randomUUID2 = input.randomUUID ??
|
|
20433
|
+
const randomUUID2 = input.randomUUID ?? crypto28.randomUUID;
|
|
20282
20434
|
const snapshot = () => ({
|
|
20283
20435
|
googleAds: cloneGoogleAdsConfig(input.config.googleAds),
|
|
20284
20436
|
gtm: cloneGtmConfig(input.config.gtm)
|
|
@@ -20395,7 +20547,7 @@ async function createServer(opts) {
|
|
|
20395
20547
|
const trustProxy = resolveTrustProxy(process.env.CANONRY_TRUST_PROXY);
|
|
20396
20548
|
const app = Fastify({
|
|
20397
20549
|
loggerInstance: logger,
|
|
20398
|
-
genReqId: () =>
|
|
20550
|
+
genReqId: () => crypto28.randomUUID(),
|
|
20399
20551
|
trustProxy
|
|
20400
20552
|
});
|
|
20401
20553
|
if (!ownsDefaultConfig) {
|
|
@@ -20526,7 +20678,7 @@ async function createServer(opts) {
|
|
|
20526
20678
|
async (ctx) => {
|
|
20527
20679
|
if (!sessionRegistry) return;
|
|
20528
20680
|
if (!agentProactive) return;
|
|
20529
|
-
const project = opts.db.select({ name: projects.name }).from(projects).where(
|
|
20681
|
+
const project = opts.db.select({ name: projects.name }).from(projects).where(eq36(projects.id, ctx.projectId)).get();
|
|
20530
20682
|
if (!project) return;
|
|
20531
20683
|
let content;
|
|
20532
20684
|
if (ctx.kind === RunKinds["aeo-discover-probe"]) {
|
|
@@ -21000,7 +21152,7 @@ async function createServer(opts) {
|
|
|
21000
21152
|
void (async () => {
|
|
21001
21153
|
try {
|
|
21002
21154
|
const report = await schedulerClient.runDoctor({ project: projectName, checkIds: scheduledHealthCheckIds() });
|
|
21003
|
-
const project = opts.db.select().from(projects).where(
|
|
21155
|
+
const project = opts.db.select().from(projects).where(eq36(projects.name, projectName)).get();
|
|
21004
21156
|
if (!project) {
|
|
21005
21157
|
app.log.warn({ projectName }, "doctor schedule fired for an unknown project");
|
|
21006
21158
|
return;
|
|
@@ -21194,7 +21346,7 @@ async function createServer(opts) {
|
|
|
21194
21346
|
return removed;
|
|
21195
21347
|
}
|
|
21196
21348
|
};
|
|
21197
|
-
const googleStateSecret = process.env.GOOGLE_STATE_SECRET ??
|
|
21349
|
+
const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto28.randomBytes(32).toString("hex");
|
|
21198
21350
|
const googleConnectionStore = {
|
|
21199
21351
|
listConnections: (domain) => listGoogleConnections(opts.config, domain),
|
|
21200
21352
|
getConnection: (domain, connectionType) => getGoogleConnection(opts.config, domain, connectionType),
|
|
@@ -21292,11 +21444,11 @@ async function createServer(opts) {
|
|
|
21292
21444
|
})();
|
|
21293
21445
|
if (opts.config.apiKey) {
|
|
21294
21446
|
const keyHash = hashApiKey(opts.config.apiKey);
|
|
21295
|
-
const existing = opts.db.select().from(apiKeys).where(
|
|
21447
|
+
const existing = opts.db.select().from(apiKeys).where(eq36(apiKeys.keyHash, keyHash)).get();
|
|
21296
21448
|
if (!existing) {
|
|
21297
21449
|
const prefix = opts.config.apiKey.slice(0, 12);
|
|
21298
21450
|
opts.db.insert(apiKeys).values({
|
|
21299
|
-
id: `key_${
|
|
21451
|
+
id: `key_${crypto28.randomBytes(8).toString("hex")}`,
|
|
21300
21452
|
name: "default",
|
|
21301
21453
|
keyHash,
|
|
21302
21454
|
keyPrefix: prefix,
|
|
@@ -21311,14 +21463,14 @@ async function createServer(opts) {
|
|
|
21311
21463
|
);
|
|
21312
21464
|
const currentPasswordFingerprint = () => hashSessionToken(opts.config.dashboardPasswordHash ?? "");
|
|
21313
21465
|
opts.db.delete(dashboardSessions).where(or4(
|
|
21314
|
-
|
|
21315
|
-
|
|
21466
|
+
eq36(dashboardSessions.apiKeyHash, ""),
|
|
21467
|
+
and30(
|
|
21316
21468
|
isNotNull5(dashboardSessions.passwordFingerprint),
|
|
21317
21469
|
ne4(dashboardSessions.passwordFingerprint, currentPasswordFingerprint())
|
|
21318
21470
|
)
|
|
21319
21471
|
)).run();
|
|
21320
21472
|
if (!opts.config.dashboardPasswordHash) {
|
|
21321
|
-
const lastPasswordEventAt = (action) => opts.db.select({ at: max(auditLog.createdAt) }).from(auditLog).where(
|
|
21473
|
+
const lastPasswordEventAt = (action) => opts.db.select({ at: max(auditLog.createdAt) }).from(auditLog).where(and30(eq36(auditLog.entityType, "dashboard-password"), eq36(auditLog.action, action))).get()?.at ?? null;
|
|
21322
21474
|
const createdAt = lastPasswordEventAt("dashboard-password.created");
|
|
21323
21475
|
const clearedAt = lastPasswordEventAt("dashboard-password.cleared");
|
|
21324
21476
|
if (createdAt !== null && (clearedAt === null || clearedAt < createdAt)) {
|
|
@@ -21333,7 +21485,7 @@ async function createServer(opts) {
|
|
|
21333
21485
|
const createSession = (key, passwordBased = false, db = opts.db) => {
|
|
21334
21486
|
const now = /* @__PURE__ */ new Date();
|
|
21335
21487
|
db.delete(dashboardSessions).where(lte3(dashboardSessions.expiresAt, now.toISOString())).run();
|
|
21336
|
-
const sessionId =
|
|
21488
|
+
const sessionId = crypto28.randomBytes(32).toString("hex");
|
|
21337
21489
|
db.insert(dashboardSessions).values({
|
|
21338
21490
|
tokenHash: hashSessionToken(sessionId),
|
|
21339
21491
|
apiKeyId: key.id,
|
|
@@ -21352,23 +21504,23 @@ async function createServer(opts) {
|
|
|
21352
21504
|
currentApiKeyHash: apiKeys.keyHash,
|
|
21353
21505
|
passwordFingerprint: dashboardSessions.passwordFingerprint,
|
|
21354
21506
|
expiresAt: dashboardSessions.expiresAt
|
|
21355
|
-
}).from(dashboardSessions).innerJoin(apiKeys,
|
|
21507
|
+
}).from(dashboardSessions).innerJoin(apiKeys, eq36(apiKeys.id, dashboardSessions.apiKeyId)).where(and30(eq36(dashboardSessions.tokenHash, tokenHash), isNull8(apiKeys.revokedAt))).get();
|
|
21356
21508
|
if (!session) return null;
|
|
21357
21509
|
const expiresAt = Date.parse(session.expiresAt);
|
|
21358
21510
|
if (!Number.isFinite(expiresAt) || expiresAt <= Date.now() || session.apiKeyHash !== session.currentApiKeyHash || session.passwordFingerprint !== null && session.passwordFingerprint !== currentPasswordFingerprint()) {
|
|
21359
|
-
opts.db.delete(dashboardSessions).where(
|
|
21511
|
+
opts.db.delete(dashboardSessions).where(eq36(dashboardSessions.tokenHash, tokenHash)).run();
|
|
21360
21512
|
return null;
|
|
21361
21513
|
}
|
|
21362
21514
|
return session.apiKeyId;
|
|
21363
21515
|
};
|
|
21364
21516
|
const clearSession = (sessionId) => {
|
|
21365
21517
|
if (sessionId) {
|
|
21366
|
-
opts.db.delete(dashboardSessions).where(
|
|
21518
|
+
opts.db.delete(dashboardSessions).where(eq36(dashboardSessions.tokenHash, hashSessionToken(sessionId))).run();
|
|
21367
21519
|
}
|
|
21368
21520
|
};
|
|
21369
21521
|
const getDefaultApiKey = () => {
|
|
21370
21522
|
if (!opts.config.apiKey) return void 0;
|
|
21371
|
-
return opts.db.select().from(apiKeys).where(
|
|
21523
|
+
return opts.db.select().from(apiKeys).where(eq36(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
|
|
21372
21524
|
};
|
|
21373
21525
|
const setSessionCookie = (reply, sessionId) => {
|
|
21374
21526
|
reply.header(
|
|
@@ -21397,7 +21549,7 @@ async function createServer(opts) {
|
|
|
21397
21549
|
if (parts.length !== 2 || parts[0] !== "Bearer" || !parts[1]) return false;
|
|
21398
21550
|
const presented = Buffer.from(hashApiKey(parts[1]), "hex");
|
|
21399
21551
|
const expected = Buffer.from(key.keyHash, "hex");
|
|
21400
|
-
return presented.length === expected.length &&
|
|
21552
|
+
return presented.length === expected.length && crypto28.timingSafeEqual(presented, expected);
|
|
21401
21553
|
};
|
|
21402
21554
|
const namedAccountsInUse = () => anyUsersExist(opts.db);
|
|
21403
21555
|
const NAMED_ACCOUNTS_MESSAGE = "This install uses named accounts. Sign in with your name and password.";
|
|
@@ -21529,12 +21681,12 @@ async function createServer(opts) {
|
|
|
21529
21681
|
return reply.send({ authenticated: true });
|
|
21530
21682
|
}
|
|
21531
21683
|
if (apiKey) {
|
|
21532
|
-
const key = opts.db.select().from(apiKeys).where(
|
|
21684
|
+
const key = opts.db.select().from(apiKeys).where(eq36(apiKeys.keyHash, hashApiKey(apiKey))).get();
|
|
21533
21685
|
if (!key || key.revokedAt) {
|
|
21534
21686
|
const err2 = authInvalid();
|
|
21535
21687
|
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
21536
21688
|
}
|
|
21537
|
-
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
21689
|
+
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq36(apiKeys.id, key.id)).run();
|
|
21538
21690
|
setSessionCookie(reply, createSession(key));
|
|
21539
21691
|
return reply.send({ authenticated: true });
|
|
21540
21692
|
}
|
|
@@ -21612,7 +21764,7 @@ async function createServer(opts) {
|
|
|
21612
21764
|
const recomputeDebts = /* @__PURE__ */ new Map();
|
|
21613
21765
|
let recomputeSequence = 0;
|
|
21614
21766
|
const markAnswerFieldRecompute = (projectId, full) => {
|
|
21615
|
-
opts.db.update(projects).set({ answerFieldsRecompute: full ? "full" : sql15`CASE WHEN ${projects.answerFieldsRecompute} = 'full' THEN 'full' ELSE 'competitors' END` }).where(
|
|
21767
|
+
opts.db.update(projects).set({ answerFieldsRecompute: full ? "full" : sql15`CASE WHEN ${projects.answerFieldsRecompute} = 'full' THEN 'full' ELSE 'competitors' END` }).where(eq36(projects.id, projectId)).run();
|
|
21616
21768
|
};
|
|
21617
21769
|
const requestAnswerFieldRecompute = (projectId, projectName, scope, reason) => {
|
|
21618
21770
|
const sequence = ++recomputeSequence;
|
|
@@ -21630,10 +21782,10 @@ async function createServer(opts) {
|
|
|
21630
21782
|
if (pass.full && debt.latestFull <= sequence) debt.latestFull = 0;
|
|
21631
21783
|
try {
|
|
21632
21784
|
if (debt.latest === sequence && debt.latestFull === 0) {
|
|
21633
|
-
opts.db.update(projects).set({ answerFieldsRecompute: null }).where(
|
|
21785
|
+
opts.db.update(projects).set({ answerFieldsRecompute: null }).where(eq36(projects.id, projectId)).run();
|
|
21634
21786
|
if (recomputeDebts.get(projectId) === debt) recomputeDebts.delete(projectId);
|
|
21635
21787
|
} else if (debt.latestFull === 0) {
|
|
21636
|
-
opts.db.update(projects).set({ answerFieldsRecompute: "competitors" }).where(
|
|
21788
|
+
opts.db.update(projects).set({ answerFieldsRecompute: "competitors" }).where(and30(eq36(projects.id, projectId), eq36(projects.answerFieldsRecompute, "full"))).run();
|
|
21637
21789
|
}
|
|
21638
21790
|
} catch (err) {
|
|
21639
21791
|
app.log.error({ err, projectId }, "could not clear the owed answer field recompute");
|
|
@@ -21670,7 +21822,7 @@ async function createServer(opts) {
|
|
|
21670
21822
|
await yieldToEventLoop4();
|
|
21671
21823
|
const reason = (competitorAutoAliasReasons.get(projectId) ?? []).join(",");
|
|
21672
21824
|
competitorAutoAliasReasons.delete(projectId);
|
|
21673
|
-
const project = opts.db.select().from(projects).where(
|
|
21825
|
+
const project = opts.db.select().from(projects).where(eq36(projects.id, projectId)).get();
|
|
21674
21826
|
if (!project || project.competitorAutoAliases === "off") return;
|
|
21675
21827
|
if (project.competitorAutoAliases === "apply") {
|
|
21676
21828
|
await competitorAutoAliases.request(projectId, reason).catch(() => void 0);
|
|
@@ -21870,8 +22022,8 @@ async function createServer(opts) {
|
|
|
21870
22022
|
getAgentPin: () => describeAgentPin(opts.config),
|
|
21871
22023
|
getGoogleMarketingDoctorInput: (ctx) => {
|
|
21872
22024
|
if (!ctx.project) return null;
|
|
21873
|
-
const googleAds = opts.db.select().from(googleAdsConnections).where(
|
|
21874
|
-
const gtm = opts.db.select().from(gtmConnections).where(
|
|
22025
|
+
const googleAds = opts.db.select().from(googleAdsConnections).where(eq36(googleAdsConnections.projectId, ctx.project.id)).get();
|
|
22026
|
+
const gtm = opts.db.select().from(gtmConnections).where(eq36(gtmConnections.projectId, ctx.project.id)).get();
|
|
21875
22027
|
const googleAdsCredential = getGoogleAdsConnection(opts.config, ctx.project.id);
|
|
21876
22028
|
const gtmCredential = getGtmConnection(opts.config, ctx.project.id);
|
|
21877
22029
|
const googleAdsConnected = Boolean(googleAdsCredential?.accessToken);
|
|
@@ -22000,7 +22152,7 @@ async function createServer(opts) {
|
|
|
22000
22152
|
deps: {
|
|
22001
22153
|
enqueueAutoExtract: ({ projectId, release: r }) => {
|
|
22002
22154
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22003
|
-
const runId =
|
|
22155
|
+
const runId = crypto28.randomUUID();
|
|
22004
22156
|
opts.db.insert(runs).values({
|
|
22005
22157
|
id: runId,
|
|
22006
22158
|
projectId,
|
|
@@ -22121,7 +22273,7 @@ async function createServer(opts) {
|
|
|
22121
22273
|
...inspectOpts,
|
|
22122
22274
|
config: opts.config
|
|
22123
22275
|
}).then(() => {
|
|
22124
|
-
const finished = opts.db.select({ status: runs.status }).from(runs).where(
|
|
22276
|
+
const finished = opts.db.select({ status: runs.status }).from(runs).where(eq36(runs.id, runId)).get();
|
|
22125
22277
|
if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
|
|
22126
22278
|
return maybeRefreshGscCoverage(
|
|
22127
22279
|
opts.db,
|
|
@@ -22633,7 +22785,7 @@ async function createServer(opts) {
|
|
|
22633
22785
|
isBacklinksInstalled: () => isDuckdbInstalled()
|
|
22634
22786
|
});
|
|
22635
22787
|
}
|
|
22636
|
-
for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(
|
|
22788
|
+
for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq36(researchRuns.status, ResearchRunStatuses.queued)).all()) {
|
|
22637
22789
|
dispatchResearchRun(run.id, run.projectId, OutcomeTriggers.startup);
|
|
22638
22790
|
}
|
|
22639
22791
|
runtimeStartupSettled = true;
|