@canonry/canonry 7.97.7 → 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-JPVLEXY6.js → chunk-IAGC2MGP.js} +357 -357
- package/dist/{chunk-5455IRP3.js → chunk-ISIJ2NS4.js} +1 -1
- package/dist/{chunk-TVVQRHVR.js → chunk-IX5AC3QB.js} +397 -224
- 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-O4X2V6EO.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-FC6ICDDA.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 +111 -1
- package/dist/index.js +25 -25
- package/dist/{init-4KLJC5N5.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-VTMTK2TK.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";
|
|
@@ -3537,8 +3538,28 @@ var serpItemSchema = z2.object({
|
|
|
3537
3538
|
domain: z2.string().nullish(),
|
|
3538
3539
|
url: z2.string().nullish(),
|
|
3539
3540
|
rank_group: z2.number().int().positive().nullish(),
|
|
3540
|
-
rank_absolute: z2.number().int().positive().nullish()
|
|
3541
|
+
rank_absolute: z2.number().int().positive().nullish(),
|
|
3542
|
+
/** The address Google shows above the title, such as `https://www.example.com › homes`. */
|
|
3543
|
+
breadcrumb: z2.string().nullish()
|
|
3541
3544
|
});
|
|
3545
|
+
function isGoogleRedirect(url) {
|
|
3546
|
+
if (!url) return false;
|
|
3547
|
+
try {
|
|
3548
|
+
const parsed = new URL(url);
|
|
3549
|
+
const host = parsed.hostname.toLowerCase().replace(/^www\./, "");
|
|
3550
|
+
return /^google\.[a-z]{2,3}(?:\.[a-z]{2})?$/.test(host) && (parsed.pathname === "/goto" || parsed.pathname === "/url");
|
|
3551
|
+
} catch {
|
|
3552
|
+
return false;
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
function resultSite(item) {
|
|
3556
|
+
if (!isGoogleRedirect(item.url)) return { host: item.domain ? normalizeHostname(item.domain) : null, url: item.url ?? null };
|
|
3557
|
+
try {
|
|
3558
|
+
return { host: normalizeHostname(new URL(item.breadcrumb?.trim().split(/[\s\u203a]/)[0] ?? "").hostname), url: null };
|
|
3559
|
+
} catch {
|
|
3560
|
+
return { host: null, url: null };
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3542
3563
|
function provenance(request, collectedAt, sourceUpdatedAt) {
|
|
3543
3564
|
const serp = request.capability === SeoCapabilities["market-rankings"];
|
|
3544
3565
|
return {
|
|
@@ -3569,15 +3590,16 @@ function marketRankings(rawResult, request, collectedAt, partial) {
|
|
|
3569
3590
|
if (searchIdentity(result.keyword) !== searchIdentity(searchText)) throw new Error("SEO provider returned a different search.");
|
|
3570
3591
|
const organic = (result.items ?? []).filter((item) => serpItemTypeSchema.safeParse(item).data?.type === "organic").map((item) => serpItemSchema.parse(item)).filter((item) => item.rank_group != null);
|
|
3571
3592
|
if (organic.length === 0) throw new Error("SEO provider returned no organic results.");
|
|
3593
|
+
const sites = organic.map((item) => ({ item, ...resultSite(item) }));
|
|
3594
|
+
if (!sites.some((site) => site.host)) throw new Error("SEO provider returned no organic result with a known site.");
|
|
3572
3595
|
const depth = partial ? Math.min(search.depth, organic.reduce((deepest, item) => Math.max(deepest, item.rank_group), 0)) : search.depth;
|
|
3573
3596
|
let best = null;
|
|
3574
3597
|
const competitorRanks = /* @__PURE__ */ new Map();
|
|
3575
3598
|
const competitors2 = new Set(search.markets.flatMap((market) => market.competitors));
|
|
3576
|
-
for (const item of
|
|
3577
|
-
const host = item.domain ? normalizeHostname(item.domain) : null;
|
|
3599
|
+
for (const { item, host, url } of sites) {
|
|
3578
3600
|
if (!host) continue;
|
|
3579
3601
|
if (hostBelongsTo(host, target)) {
|
|
3580
|
-
if (!best || item.rank_group < best.
|
|
3602
|
+
if (!best || item.rank_group < best.rank) best = { rank: item.rank_group, rankAbsolute: item.rank_absolute ?? null, url };
|
|
3581
3603
|
continue;
|
|
3582
3604
|
}
|
|
3583
3605
|
const domain = registrableDomain(host);
|
|
@@ -3594,8 +3616,8 @@ function marketRankings(rawResult, request, collectedAt, partial) {
|
|
|
3594
3616
|
locationLabel: search.locationLabel,
|
|
3595
3617
|
target,
|
|
3596
3618
|
searchStatus: partial ? SeoMarketRankingSearchStatuses.partial : SeoMarketRankingSearchStatuses.completed,
|
|
3597
|
-
rankOrganic: best?.
|
|
3598
|
-
rankAbsolute: best?.
|
|
3619
|
+
rankOrganic: best?.rank ?? null,
|
|
3620
|
+
rankAbsolute: best?.rankAbsolute ?? null,
|
|
3599
3621
|
url: best?.url ?? null,
|
|
3600
3622
|
depth,
|
|
3601
3623
|
competitors: ranked.filter((competitor) => tracked.has(competitor.domain)),
|
|
@@ -5980,7 +6002,7 @@ function nonBlank(value) {
|
|
|
5980
6002
|
return trimmed ? trimmed : void 0;
|
|
5981
6003
|
}
|
|
5982
6004
|
function resolveBuildCommit(env = process.env) {
|
|
5983
|
-
const embedded = true ? "
|
|
6005
|
+
const embedded = true ? "161453e7b8d6e3b33614392ce97134fc484106ec" : void 0;
|
|
5984
6006
|
return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
|
|
5985
6007
|
}
|
|
5986
6008
|
function resolveInstanceIdentity(env = process.env) {
|
|
@@ -15118,8 +15140,85 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
|
|
|
15118
15140
|
}
|
|
15119
15141
|
|
|
15120
15142
|
// src/notifier.ts
|
|
15121
|
-
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
|
|
15122
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
|
|
15123
15222
|
var log24 = createLogger("Notifier");
|
|
15124
15223
|
var sampleDelivery = createOutcomeSampler({ burst: 20, refillMs: 6e4, now: () => Date.now() });
|
|
15125
15224
|
function trackWebhookDelivery(input) {
|
|
@@ -15241,18 +15340,18 @@ var Notifier = class {
|
|
|
15241
15340
|
/** Called after a run completes (success, partial, or failed). */
|
|
15242
15341
|
async onRunCompleted(runId, projectId, opts) {
|
|
15243
15342
|
log24.info("run.completed", { runId, projectId, origin: opts?.origin ?? "sweep" });
|
|
15244
|
-
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);
|
|
15245
15344
|
if (notifs.length === 0) {
|
|
15246
15345
|
log24.info("notifications.none-enabled", { projectId });
|
|
15247
15346
|
return;
|
|
15248
15347
|
}
|
|
15249
15348
|
log24.info("notifications.found", { projectId, count: notifs.length });
|
|
15250
|
-
const run = this.db.select().from(runs).where(
|
|
15349
|
+
const run = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
|
|
15251
15350
|
if (!run) {
|
|
15252
15351
|
log24.error("run.not-found", { runId, msg: "skipping notification dispatch" });
|
|
15253
15352
|
return;
|
|
15254
15353
|
}
|
|
15255
|
-
const project = this.db.select().from(projects).where(
|
|
15354
|
+
const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
15256
15355
|
if (!project) {
|
|
15257
15356
|
log24.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
|
|
15258
15357
|
return;
|
|
@@ -15331,7 +15430,7 @@ var Notifier = class {
|
|
|
15331
15430
|
* than inferred from the absence of a webhook.
|
|
15332
15431
|
*/
|
|
15333
15432
|
async onHealthChecked(projectId, report) {
|
|
15334
|
-
const project = this.db.select().from(projects).where(
|
|
15433
|
+
const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
15335
15434
|
if (!project) {
|
|
15336
15435
|
log24.error("project.not-found", { projectId, msg: "skipping health notification" });
|
|
15337
15436
|
return null;
|
|
@@ -15364,7 +15463,7 @@ var Notifier = class {
|
|
|
15364
15463
|
const status = noSignal ? "warn" : worst ? worst.status : "ok";
|
|
15365
15464
|
const code = noSignal ? HEALTH_NO_SIGNAL_CODE : worst?.code ?? "health.ok";
|
|
15366
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.`;
|
|
15367
|
-
const previous = this.db.select().from(doctorHealthState).where(
|
|
15466
|
+
const previous = this.db.select().from(doctorHealthState).where(eq26(doctorHealthState.projectId, projectId)).get();
|
|
15368
15467
|
const previousStatus = previous?.status ?? null;
|
|
15369
15468
|
const failingSignature = failing.map((c) => `${c.status}:${c.code}`).join(",");
|
|
15370
15469
|
const failingNow = failing.map((c) => ({ id: c.id, code: c.code, rank: rank(c.status) }));
|
|
@@ -15434,7 +15533,7 @@ var Notifier = class {
|
|
|
15434
15533
|
const urgentById = new Map(urgentItems.map((item) => [item.id, item]));
|
|
15435
15534
|
const generalEvent = event;
|
|
15436
15535
|
const urgentRecord = previous?.urgentNotified ?? null;
|
|
15437
|
-
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) : [];
|
|
15438
15537
|
const listedEvents = (notif) => notif.config.events;
|
|
15439
15538
|
const hasUrl = (notif) => Boolean(notif.config.url);
|
|
15440
15539
|
const urgentSubscribers = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
|
|
@@ -15482,7 +15581,7 @@ var Notifier = class {
|
|
|
15482
15581
|
if (previous === void 0) {
|
|
15483
15582
|
this.db.insert(doctorHealthState).values({ ...observation, notifiedAt: null }).run();
|
|
15484
15583
|
} else {
|
|
15485
|
-
this.db.update(doctorHealthState).set(observation).where(
|
|
15584
|
+
this.db.update(doctorHealthState).set(observation).where(eq26(doctorHealthState.projectId, projectId)).run();
|
|
15486
15585
|
}
|
|
15487
15586
|
for (const group of urgentGroups) {
|
|
15488
15587
|
const { urgentEvent, urgentTold, urgentNew, urgentCleared, urgentNotifs, urgentSettled } = group;
|
|
@@ -15526,7 +15625,7 @@ var Notifier = class {
|
|
|
15526
15625
|
}
|
|
15527
15626
|
if (accepted > 0) {
|
|
15528
15627
|
recordUrgentReceipts(urgentReceipts, group.subscribers, formatUrgentBreaches(urgentSettled) || null);
|
|
15529
|
-
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();
|
|
15530
15629
|
}
|
|
15531
15630
|
if (urgentNotifs.length > 0) {
|
|
15532
15631
|
log24.info("health.urgent-notified", {
|
|
@@ -15570,7 +15669,7 @@ var Notifier = class {
|
|
|
15570
15669
|
if (await this.sendWebhook(config.url, payload, notif.id, projectId, notif.webhookSecret ?? null)) delivered += 1;
|
|
15571
15670
|
}
|
|
15572
15671
|
if (delivered > 0) {
|
|
15573
|
-
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();
|
|
15574
15673
|
}
|
|
15575
15674
|
log24.info("health.notified", {
|
|
15576
15675
|
projectId,
|
|
@@ -15600,12 +15699,12 @@ var Notifier = class {
|
|
|
15600
15699
|
async onSiteLivenessChecked(projectId, result) {
|
|
15601
15700
|
const { check, checkedAt } = result;
|
|
15602
15701
|
if (check.status !== "ok" && check.status !== "fail") return null;
|
|
15603
|
-
const project = this.db.select().from(projects).where(
|
|
15702
|
+
const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
15604
15703
|
if (!project) {
|
|
15605
15704
|
log24.error("project.not-found", { projectId, msg: "skipping site liveness notification" });
|
|
15606
15705
|
return null;
|
|
15607
15706
|
}
|
|
15608
|
-
const previous = this.db.select().from(siteLivenessState).where(
|
|
15707
|
+
const previous = this.db.select().from(siteLivenessState).where(eq26(siteLivenessState.projectId, projectId)).get();
|
|
15609
15708
|
const sincePrevious = previous ? Date.parse(checkedAt) - Date.parse(previous.checkedAt) : Number.NaN;
|
|
15610
15709
|
const countsAsRepeat = Number.isFinite(sincePrevious) && sincePrevious >= SITE_LIVENESS_MIN_PASS_GAP_MS && sincePrevious <= SITE_LIVENESS_MAX_PASS_GAP_MS;
|
|
15611
15710
|
const failures = check.status === "fail" ? countsAsRepeat ? (previous?.consecutiveFailures ?? 0) + 1 : 1 : 0;
|
|
@@ -15615,7 +15714,7 @@ var Notifier = class {
|
|
|
15615
15714
|
if (check.status === "ok" && outagePaged) event = "health.recovered";
|
|
15616
15715
|
const confirmed = check.status === "fail" && failures >= SITE_LIVENESS_FAILURES_TO_PAGE;
|
|
15617
15716
|
const urgentRecord = previous?.urgentNotifiedAt ?? null;
|
|
15618
|
-
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) : [];
|
|
15619
15718
|
const listedEvents = (notif) => notif.config.events;
|
|
15620
15719
|
const hasUrl = (notif) => Boolean(notif.config.url);
|
|
15621
15720
|
const scoped = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
|
|
@@ -15648,7 +15747,7 @@ var Notifier = class {
|
|
|
15648
15747
|
if (previous === void 0) {
|
|
15649
15748
|
this.db.insert(siteLivenessState).values(observation).run();
|
|
15650
15749
|
} else {
|
|
15651
|
-
this.db.update(siteLivenessState).set(observation).where(
|
|
15750
|
+
this.db.update(siteLivenessState).set(observation).where(eq26(siteLivenessState.projectId, projectId)).run();
|
|
15652
15751
|
}
|
|
15653
15752
|
if (!event && urgentGroups.every((group) => group.urgentEvent === null)) return null;
|
|
15654
15753
|
const payloadFor = (name) => {
|
|
@@ -15684,7 +15783,7 @@ var Notifier = class {
|
|
|
15684
15783
|
}
|
|
15685
15784
|
}
|
|
15686
15785
|
if (event === "health.degraded" && delivered > 0) {
|
|
15687
|
-
this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(
|
|
15786
|
+
this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(eq26(siteLivenessState.projectId, projectId)).run();
|
|
15688
15787
|
}
|
|
15689
15788
|
log24.info("site-liveness.notified", { projectId, event, code: check.code, subscribers: notifs.length, delivered });
|
|
15690
15789
|
}
|
|
@@ -15698,7 +15797,7 @@ var Notifier = class {
|
|
|
15698
15797
|
}
|
|
15699
15798
|
if (accepted > 0) {
|
|
15700
15799
|
recordUrgentReceipts(urgentReceipts, group.subscribers, group.settled);
|
|
15701
|
-
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();
|
|
15702
15801
|
}
|
|
15703
15802
|
if (urgentNotifs.length > 0 || generalIds.size > 0) {
|
|
15704
15803
|
log24.info("site-liveness.urgent-notified", { projectId, event: urgentEvent, code: check.code, subscribers: urgentNotifs.length + generalIds.size, delivered: accepted });
|
|
@@ -15724,7 +15823,7 @@ var Notifier = class {
|
|
|
15724
15823
|
*/
|
|
15725
15824
|
isNewInsightNews(projectId, insight) {
|
|
15726
15825
|
const key = insightNotifyKey(projectId, insight);
|
|
15727
|
-
const previous = this.db.select().from(insightNotifyState).where(
|
|
15826
|
+
const previous = this.db.select().from(insightNotifyState).where(eq26(insightNotifyState.key, key)).get();
|
|
15728
15827
|
if (!previous) return true;
|
|
15729
15828
|
if (severityRank(insight.severity) > severityRank(previous.severity)) return true;
|
|
15730
15829
|
const magnitude = insightMagnitude(insight);
|
|
@@ -15746,9 +15845,9 @@ var Notifier = class {
|
|
|
15746
15845
|
magnitude: insightMagnitude(insight),
|
|
15747
15846
|
notifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15748
15847
|
};
|
|
15749
|
-
const existing = this.db.select().from(insightNotifyState).where(
|
|
15848
|
+
const existing = this.db.select().from(insightNotifyState).where(eq26(insightNotifyState.key, key)).get();
|
|
15750
15849
|
if (existing) {
|
|
15751
|
-
this.db.update(insightNotifyState).set(row).where(
|
|
15850
|
+
this.db.update(insightNotifyState).set(row).where(eq26(insightNotifyState.key, key)).run();
|
|
15752
15851
|
} else {
|
|
15753
15852
|
this.db.insert(insightNotifyState).values(row).run();
|
|
15754
15853
|
}
|
|
@@ -15762,11 +15861,11 @@ var Notifier = class {
|
|
|
15762
15861
|
if (criticalInsights.length > 0) insightEvents.push("insight.critical");
|
|
15763
15862
|
if (highInsights.length > 0) insightEvents.push("insight.high");
|
|
15764
15863
|
if (insightEvents.length === 0) return;
|
|
15765
|
-
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);
|
|
15766
15865
|
if (notifs.length === 0) return;
|
|
15767
|
-
const run = this.db.select().from(runs).where(
|
|
15866
|
+
const run = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
|
|
15768
15867
|
if (!run) return;
|
|
15769
|
-
const project = this.db.select().from(projects).where(
|
|
15868
|
+
const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
15770
15869
|
if (!project) return;
|
|
15771
15870
|
for (const notif of notifs) {
|
|
15772
15871
|
const config = notif.config;
|
|
@@ -15847,9 +15946,9 @@ var Notifier = class {
|
|
|
15847
15946
|
return delivered;
|
|
15848
15947
|
}
|
|
15849
15948
|
async dispatchQueuedReviewAlerts(projectId) {
|
|
15850
|
-
const pendingReviews = this.db.select().from(gbpReviews).where(
|
|
15851
|
-
const pendingRatings = this.db.select().from(gbpReviewRatings).where(
|
|
15852
|
-
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();
|
|
15853
15952
|
if (!project) return { reviews: 0, ratingDrops: 0, delivered: 0 };
|
|
15854
15953
|
const chatSendsLeft = /* @__PURE__ */ new Map();
|
|
15855
15954
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -15861,10 +15960,10 @@ var Notifier = class {
|
|
|
15861
15960
|
displayName: gbpLocations.displayName,
|
|
15862
15961
|
mapsUri: gbpLocations.mapsUri,
|
|
15863
15962
|
reviewsAccess: gbpLocations.reviewsAccess
|
|
15864
|
-
}).from(gbpLocations).where(
|
|
15963
|
+
}).from(gbpLocations).where(eq26(gbpLocations.projectId, projectId)).all().map((loc) => [loc.locationName, loc])
|
|
15865
15964
|
);
|
|
15866
15965
|
const v4HasWorked = new Set(
|
|
15867
|
-
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)
|
|
15868
15967
|
);
|
|
15869
15968
|
const coveredByBusinessProfile = (locationName) => businessProfileReviewsCover(locations.get(locationName)?.reviewsAccess ?? null, v4HasWorked.has(locationName));
|
|
15870
15969
|
const locationRef = (locationName) => {
|
|
@@ -15893,7 +15992,7 @@ var Notifier = class {
|
|
|
15893
15992
|
else if (row.rating === null || row.previousRating === null) setRatingState([row.id], "none");
|
|
15894
15993
|
else ratingsToSend.push(row);
|
|
15895
15994
|
}
|
|
15896
|
-
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);
|
|
15897
15996
|
const subscribedTo = (event) => enabled.filter((n) => (n.config.events ?? []).includes(event));
|
|
15898
15997
|
const projectRef = { name: project.name, canonicalDomain: project.canonicalDomain };
|
|
15899
15998
|
const dashboardUrl = `${this.serverUrl}/projects/${project.name}`;
|
|
@@ -15947,13 +16046,13 @@ var Notifier = class {
|
|
|
15947
16046
|
if (!sent) all = false;
|
|
15948
16047
|
}
|
|
15949
16048
|
if (all) {
|
|
15950
|
-
this.db.update(gbpReviews).set({ alertState: "sent", alertStateAt: nowIso }).where(
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
|
|
15954
|
-
review.alertStateAt === null ? isNull5(gbpReviews.alertStateAt) :
|
|
15955
|
-
review.comment === null ? isNull5(gbpReviews.comment) :
|
|
15956
|
-
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)
|
|
15957
16056
|
)).run();
|
|
15958
16057
|
}
|
|
15959
16058
|
}
|
|
@@ -15994,7 +16093,7 @@ var Notifier = class {
|
|
|
15994
16093
|
return { reviews: reviewsToSend.length, ratingDrops: ratingsToSend.length, delivered };
|
|
15995
16094
|
}
|
|
15996
16095
|
computeTransitions(runId, projectId) {
|
|
15997
|
-
const thisRun = this.db.select().from(runs).where(
|
|
16096
|
+
const thisRun = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
|
|
15998
16097
|
if (!thisRun) return [];
|
|
15999
16098
|
const completeness = measurementRunCompleteness(this.db, runId);
|
|
16000
16099
|
if (completeness.planned && !completeness.complete) {
|
|
@@ -16005,10 +16104,10 @@ var Notifier = class {
|
|
|
16005
16104
|
});
|
|
16006
16105
|
return [];
|
|
16007
16106
|
}
|
|
16008
|
-
const groupSiblings = this.db.select().from(runs).where(
|
|
16009
|
-
|
|
16010
|
-
|
|
16011
|
-
|
|
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),
|
|
16012
16111
|
notProbeRun()
|
|
16013
16112
|
)).all();
|
|
16014
16113
|
const stillPending = groupSiblings.some((r) => r.status === "queued" || r.status === "running");
|
|
@@ -16025,20 +16124,20 @@ var Notifier = class {
|
|
|
16025
16124
|
return candidate.id > best.id ? candidate : best;
|
|
16026
16125
|
});
|
|
16027
16126
|
if (winner.id !== runId) return [];
|
|
16028
|
-
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();
|
|
16029
16128
|
const locationCount = Math.max(
|
|
16030
16129
|
1,
|
|
16031
16130
|
(projectLocations?.locations ?? []).length
|
|
16032
16131
|
);
|
|
16033
16132
|
const RECENT_FETCH_LIMIT = Math.max(8, locationCount * 4);
|
|
16034
16133
|
const recentRuns = this.db.select().from(runs).where(
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
or2(
|
|
16134
|
+
and23(
|
|
16135
|
+
eq26(runs.projectId, projectId),
|
|
16136
|
+
eq26(runs.kind, thisRun.kind),
|
|
16137
|
+
or2(eq26(runs.status, "completed"), eq26(runs.status, "partial")),
|
|
16039
16138
|
notProbeRun()
|
|
16040
16139
|
)
|
|
16041
|
-
).orderBy(
|
|
16140
|
+
).orderBy(desc10(runs.createdAt), desc10(runs.id)).limit(RECENT_FETCH_LIMIT).all();
|
|
16042
16141
|
const groups = groupRunsByCreatedAt(recentRuns);
|
|
16043
16142
|
const currentGroupIdx = groups.findIndex((g) => g[0]?.createdAt === thisRun.createdAt);
|
|
16044
16143
|
if (currentGroupIdx < 0) return [];
|
|
@@ -16053,7 +16152,7 @@ var Notifier = class {
|
|
|
16053
16152
|
provider: querySnapshots.provider,
|
|
16054
16153
|
location: querySnapshots.location,
|
|
16055
16154
|
citationState: querySnapshots.citationState
|
|
16056
|
-
}).from(querySnapshots).leftJoin(queries,
|
|
16155
|
+
}).from(querySnapshots).leftJoin(queries, eq26(querySnapshots.queryId, queries.id)).where(inArray12(querySnapshots.runId, currentRunIds)).all();
|
|
16057
16156
|
const previousSnapshots = this.db.select({
|
|
16058
16157
|
queryId: querySnapshots.queryId,
|
|
16059
16158
|
provider: querySnapshots.provider,
|
|
@@ -16104,12 +16203,12 @@ var Notifier = class {
|
|
|
16104
16203
|
recordUnrecognizedAgentDecisions(this.db, projectId, baseline, UnrecognizedAgentNotifyStates.baseline, now);
|
|
16105
16204
|
if (due.length === 0) continue;
|
|
16106
16205
|
const names = due.map((agent) => agent.name);
|
|
16107
|
-
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"));
|
|
16108
16207
|
if (subscribed.length === 0) {
|
|
16109
16208
|
recordUnrecognizedAgentDecisions(this.db, projectId, names, UnrecognizedAgentNotifyStates.skipped, now);
|
|
16110
16209
|
continue;
|
|
16111
16210
|
}
|
|
16112
|
-
const project = this.db.select().from(projects).where(
|
|
16211
|
+
const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
16113
16212
|
if (!project) continue;
|
|
16114
16213
|
const payload = {
|
|
16115
16214
|
source: "canonry",
|
|
@@ -16132,6 +16231,73 @@ var Notifier = class {
|
|
|
16132
16231
|
}
|
|
16133
16232
|
return sentNames;
|
|
16134
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
|
+
}
|
|
16135
16301
|
async sendWebhook(url, payload, notificationId, projectId, webhookSecret) {
|
|
16136
16302
|
const elapsed = startOutcomeTimer();
|
|
16137
16303
|
const destination = resolveDestination(url);
|
|
@@ -16199,7 +16365,7 @@ var Notifier = class {
|
|
|
16199
16365
|
}
|
|
16200
16366
|
logDelivery(projectId, notificationId, event, status, error) {
|
|
16201
16367
|
this.db.insert(auditLog).values({
|
|
16202
|
-
id:
|
|
16368
|
+
id: crypto23.randomUUID(),
|
|
16203
16369
|
projectId,
|
|
16204
16370
|
actor: "scheduler",
|
|
16205
16371
|
action: `notification.${status}`,
|
|
@@ -16212,7 +16378,7 @@ var Notifier = class {
|
|
|
16212
16378
|
};
|
|
16213
16379
|
|
|
16214
16380
|
// src/run-coordinator.ts
|
|
16215
|
-
import { eq as
|
|
16381
|
+
import { eq as eq27 } from "drizzle-orm";
|
|
16216
16382
|
var log25 = createLogger("RunCoordinator");
|
|
16217
16383
|
function reportInsightsGenerated(runTrigger, outcome) {
|
|
16218
16384
|
trackFeatureCompleted({
|
|
@@ -16246,7 +16412,7 @@ var RunCoordinator = class {
|
|
|
16246
16412
|
*/
|
|
16247
16413
|
onAnswersRecorded;
|
|
16248
16414
|
async onRunCompleted(runId, projectId, opts) {
|
|
16249
|
-
const runRow = this.db.select().from(runs).where(
|
|
16415
|
+
const runRow = this.db.select().from(runs).where(eq27(runs.id, runId)).get();
|
|
16250
16416
|
const kind = runRow?.kind ?? RunKinds["answer-visibility"];
|
|
16251
16417
|
if (runRow?.trigger === RunTriggers.probe) {
|
|
16252
16418
|
log25.info("probe.skip-side-effects", { runId, projectId, kind });
|
|
@@ -16340,6 +16506,13 @@ var RunCoordinator = class {
|
|
|
16340
16506
|
} catch (err) {
|
|
16341
16507
|
log25.error("notifier.failed", { runId, error: describeError(err) });
|
|
16342
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
|
+
}
|
|
16343
16516
|
if (this.onAeroEvent) {
|
|
16344
16517
|
try {
|
|
16345
16518
|
const ctx = kind === RunKinds["aeo-discover-probe"] ? this.buildDiscoveryAeroContext(runId, projectId, runRow?.status === "failed" ? "failed" : "completed", runRow?.error ?? null) : {
|
|
@@ -16366,7 +16539,7 @@ var RunCoordinator = class {
|
|
|
16366
16539
|
* so the Aero queue is never starved of a follow-up.
|
|
16367
16540
|
*/
|
|
16368
16541
|
buildDiscoveryAeroContext(runId, projectId, status, error) {
|
|
16369
|
-
const session = this.db.select().from(discoverySessions).where(
|
|
16542
|
+
const session = this.db.select().from(discoverySessions).where(eq27(discoverySessions.runId, runId)).get();
|
|
16370
16543
|
const competitorMap = session ? session.competitorMap : [];
|
|
16371
16544
|
return {
|
|
16372
16545
|
kind: RunKinds["aeo-discover-probe"],
|
|
@@ -16408,8 +16581,8 @@ function analysisResultFromInsights(insights2) {
|
|
|
16408
16581
|
}
|
|
16409
16582
|
|
|
16410
16583
|
// src/agent/session-registry.ts
|
|
16411
|
-
import
|
|
16412
|
-
import { eq as
|
|
16584
|
+
import crypto25 from "crypto";
|
|
16585
|
+
import { eq as eq30 } from "drizzle-orm";
|
|
16413
16586
|
|
|
16414
16587
|
// src/agent/remote-mcp.ts
|
|
16415
16588
|
import { Type } from "@earendil-works/pi-ai";
|
|
@@ -16513,8 +16686,8 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
16513
16686
|
}
|
|
16514
16687
|
|
|
16515
16688
|
// src/agent/memory-store.ts
|
|
16516
|
-
import
|
|
16517
|
-
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";
|
|
16518
16691
|
var COMPACTION_KEY_PREFIX = "compaction:";
|
|
16519
16692
|
var COMPACTION_NOTES_PER_SESSION = 3;
|
|
16520
16693
|
function rowToDto(row) {
|
|
@@ -16528,7 +16701,7 @@ function rowToDto(row) {
|
|
|
16528
16701
|
};
|
|
16529
16702
|
}
|
|
16530
16703
|
function listMemoryEntries(db, projectId, opts = {}) {
|
|
16531
|
-
const query = db.select().from(agentMemory).where(
|
|
16704
|
+
const query = db.select().from(agentMemory).where(eq28(agentMemory.projectId, projectId)).orderBy(desc11(agentMemory.updatedAt));
|
|
16532
16705
|
const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
|
|
16533
16706
|
return rows.map(rowToDto);
|
|
16534
16707
|
}
|
|
@@ -16542,7 +16715,7 @@ function upsertMemoryEntry(db, args) {
|
|
|
16542
16715
|
throw new Error(`memory key prefix "${COMPACTION_KEY_PREFIX}" is reserved for compaction notes`);
|
|
16543
16716
|
}
|
|
16544
16717
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16545
|
-
const id =
|
|
16718
|
+
const id = crypto24.randomUUID();
|
|
16546
16719
|
db.insert(agentMemory).values({
|
|
16547
16720
|
id,
|
|
16548
16721
|
projectId: args.projectId,
|
|
@@ -16559,22 +16732,22 @@ function upsertMemoryEntry(db, args) {
|
|
|
16559
16732
|
updatedAt: now
|
|
16560
16733
|
}
|
|
16561
16734
|
}).run();
|
|
16562
|
-
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();
|
|
16563
16736
|
if (!row) throw new Error("memory upsert produced no row");
|
|
16564
16737
|
return rowToDto(row);
|
|
16565
16738
|
}
|
|
16566
16739
|
function deleteMemoryEntry(db, projectId, key) {
|
|
16567
|
-
const result = db.delete(agentMemory).where(
|
|
16740
|
+
const result = db.delete(agentMemory).where(and24(eq28(agentMemory.projectId, projectId), eq28(agentMemory.key, key))).run();
|
|
16568
16741
|
const changes = result.changes ?? 0;
|
|
16569
16742
|
return changes > 0;
|
|
16570
16743
|
}
|
|
16571
16744
|
function loadRecentForHydrate(db, projectId, limit, sessionId) {
|
|
16572
16745
|
if (sessionId === void 0) return listMemoryEntries(db, projectId, { limit });
|
|
16573
16746
|
const sessionPrefix = `compaction:${sessionId ?? ""}:`;
|
|
16574
|
-
return db.select().from(agentMemory).where(
|
|
16575
|
-
|
|
16747
|
+
return db.select().from(agentMemory).where(and24(
|
|
16748
|
+
eq28(agentMemory.projectId, projectId),
|
|
16576
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)
|
|
16577
|
-
)).orderBy(
|
|
16750
|
+
)).orderBy(desc11(agentMemory.updatedAt)).limit(limit).all().map(rowToDto);
|
|
16578
16751
|
}
|
|
16579
16752
|
function writeCompactionNote(db, args) {
|
|
16580
16753
|
if (Buffer.byteLength(args.summary, "utf8") > AGENT_MEMORY_VALUE_MAX_BYTES) {
|
|
@@ -16584,7 +16757,7 @@ function writeCompactionNote(db, args) {
|
|
|
16584
16757
|
}
|
|
16585
16758
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16586
16759
|
const key = `${COMPACTION_KEY_PREFIX}${args.sessionId}:${now}`;
|
|
16587
|
-
const id =
|
|
16760
|
+
const id = crypto24.randomUUID();
|
|
16588
16761
|
let inserted;
|
|
16589
16762
|
db.transaction((tx) => {
|
|
16590
16763
|
tx.insert(agentMemory).values({
|
|
@@ -16598,16 +16771,16 @@ function writeCompactionNote(db, args) {
|
|
|
16598
16771
|
}).run();
|
|
16599
16772
|
const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
|
|
16600
16773
|
const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
|
|
16601
|
-
|
|
16602
|
-
|
|
16774
|
+
and24(
|
|
16775
|
+
eq28(agentMemory.projectId, args.projectId),
|
|
16603
16776
|
like(agentMemory.key, `${sessionPrefix}%`)
|
|
16604
16777
|
)
|
|
16605
|
-
).orderBy(
|
|
16778
|
+
).orderBy(desc11(agentMemory.updatedAt)).all();
|
|
16606
16779
|
const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
|
|
16607
16780
|
if (stale.length > 0) {
|
|
16608
16781
|
tx.delete(agentMemory).where(sql12`${agentMemory.id} IN (${sql12.join(stale.map((s) => sql12`${s}`), sql12`, `)})`).run();
|
|
16609
16782
|
}
|
|
16610
|
-
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();
|
|
16611
16784
|
if (row) inserted = rowToDto(row);
|
|
16612
16785
|
});
|
|
16613
16786
|
if (!inserted) throw new Error("compaction note write produced no row");
|
|
@@ -16681,7 +16854,7 @@ function trackAeroTurn(agent, turn) {
|
|
|
16681
16854
|
}
|
|
16682
16855
|
|
|
16683
16856
|
// src/agent/project-shape.ts
|
|
16684
|
-
import { and as
|
|
16857
|
+
import { and as and25, count as count5, eq as eq29 } from "drizzle-orm";
|
|
16685
16858
|
var ADVANCED_TOOLS = [
|
|
16686
16859
|
"canonry_measurement_portfolio_summary",
|
|
16687
16860
|
"canonry_measurement_overview",
|
|
@@ -16708,10 +16881,10 @@ var ADVANCED_ROUTES = [
|
|
|
16708
16881
|
];
|
|
16709
16882
|
function aeroProjectShape(db, projectId) {
|
|
16710
16883
|
try {
|
|
16711
|
-
const pointer = db.select().from(measurementPlans).where(
|
|
16712
|
-
const version = pointer ? db.select().from(measurementPlanVersions).where(
|
|
16713
|
-
|
|
16714
|
-
|
|
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)
|
|
16715
16888
|
)).get() : void 0;
|
|
16716
16889
|
if (version) {
|
|
16717
16890
|
const plan = JSON.parse(version.canonicalJson);
|
|
@@ -16738,7 +16911,7 @@ canonry_visibility_report describes the whole project, not one Property. canonry
|
|
|
16738
16911
|
pinned: ADVANCED_TOOLS
|
|
16739
16912
|
};
|
|
16740
16913
|
}
|
|
16741
|
-
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;
|
|
16742
16915
|
return {
|
|
16743
16916
|
prompt: `
|
|
16744
16917
|
|
|
@@ -16997,7 +17170,7 @@ var SessionRegistry = class {
|
|
|
16997
17170
|
modelProvider: effectiveProvider,
|
|
16998
17171
|
modelId: effectiveModelId,
|
|
16999
17172
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17000
|
-
}).where(
|
|
17173
|
+
}).where(eq30(agentSessions.projectId, projectId)).run();
|
|
17001
17174
|
}
|
|
17002
17175
|
const agent2 = createAeroSession({
|
|
17003
17176
|
projectName,
|
|
@@ -17026,7 +17199,7 @@ var SessionRegistry = class {
|
|
|
17026
17199
|
return agent2;
|
|
17027
17200
|
}
|
|
17028
17201
|
const { provider, modelId } = resolveSessionProviderAndModel(this.opts.config, preferences);
|
|
17029
|
-
const sessionId =
|
|
17202
|
+
const sessionId = crypto25.randomUUID();
|
|
17030
17203
|
const agent = createAeroSession({
|
|
17031
17204
|
projectName,
|
|
17032
17205
|
client: this.opts.client,
|
|
@@ -17316,7 +17489,7 @@ ${lines.join("\n")}
|
|
|
17316
17489
|
modelProvider: nextProvider,
|
|
17317
17490
|
modelId: nextModelId,
|
|
17318
17491
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17319
|
-
}).where(
|
|
17492
|
+
}).where(eq30(agentSessions.projectId, projectId)).run();
|
|
17320
17493
|
}
|
|
17321
17494
|
/** Snapshot pending work without consuming it before an atomic conversation switch. */
|
|
17322
17495
|
prepareConversationChange(projectName) {
|
|
@@ -17524,7 +17697,7 @@ ${lines.join("\n")}
|
|
|
17524
17697
|
providerAccountKey(provider, apiKey) {
|
|
17525
17698
|
const canonical = coerceAgentProvider(provider);
|
|
17526
17699
|
const vendor = canonical ? getAgentProvider(canonical).piAiProvider : provider;
|
|
17527
|
-
return
|
|
17700
|
+
return crypto25.createHash("sha256").update(JSON.stringify([vendor, apiKey ?? ""])).digest("hex");
|
|
17528
17701
|
}
|
|
17529
17702
|
providerCoolingDown(projectName) {
|
|
17530
17703
|
for (const [key, cooldown] of this.providerCooldowns) {
|
|
@@ -17541,18 +17714,18 @@ ${lines.join("\n")}
|
|
|
17541
17714
|
return id;
|
|
17542
17715
|
}
|
|
17543
17716
|
tryResolveProjectId(projectName) {
|
|
17544
|
-
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();
|
|
17545
17718
|
return row?.id;
|
|
17546
17719
|
}
|
|
17547
17720
|
loadRow(projectId) {
|
|
17548
|
-
const row = this.opts.db.select().from(agentSessions).where(
|
|
17721
|
+
const row = this.opts.db.select().from(agentSessions).where(eq30(agentSessions.projectId, projectId)).get();
|
|
17549
17722
|
return row ?? null;
|
|
17550
17723
|
}
|
|
17551
17724
|
insertRow(params) {
|
|
17552
17725
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
17553
17726
|
const modelProvider = params.provider ?? params.modelProvider ?? AgentProviderIds.claude;
|
|
17554
17727
|
this.opts.db.insert(agentSessions).values({
|
|
17555
|
-
id: params.id ??
|
|
17728
|
+
id: params.id ?? crypto25.randomUUID(),
|
|
17556
17729
|
projectId: params.projectId,
|
|
17557
17730
|
systemPrompt: params.systemPrompt,
|
|
17558
17731
|
modelProvider,
|
|
@@ -17565,7 +17738,7 @@ ${lines.join("\n")}
|
|
|
17565
17738
|
}
|
|
17566
17739
|
updateRow(projectId, patch) {
|
|
17567
17740
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
17568
|
-
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();
|
|
17569
17742
|
}
|
|
17570
17743
|
/**
|
|
17571
17744
|
* Store a refreshed prompt snapshot without touching `updatedAt`, which the
|
|
@@ -17574,13 +17747,13 @@ ${lines.join("\n")}
|
|
|
17574
17747
|
* recently used right after an upgrade.
|
|
17575
17748
|
*/
|
|
17576
17749
|
persistPromptSnapshot(projectId, systemPrompt) {
|
|
17577
|
-
this.opts.db.update(agentSessions).set({ systemPrompt }).where(
|
|
17750
|
+
this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq30(agentSessions.projectId, projectId)).run();
|
|
17578
17751
|
}
|
|
17579
17752
|
};
|
|
17580
17753
|
|
|
17581
17754
|
// src/agent/viewer-sessions.ts
|
|
17582
|
-
import
|
|
17583
|
-
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";
|
|
17584
17757
|
var AERO_VIEWER_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
|
|
17585
17758
|
CanonryMcpToolNames.canonry_ads_account,
|
|
17586
17759
|
CanonryMcpToolNames.canonry_ads_geo_search,
|
|
@@ -17633,8 +17806,8 @@ function trimViewerTranscript(messages, max2 = MAX_VIEWER_MESSAGES) {
|
|
|
17633
17806
|
}
|
|
17634
17807
|
var VIEWER_IDLE_MS = 12 * 60 * 60 * 1e3;
|
|
17635
17808
|
function mintViewerAeroKey(db, userId, now = /* @__PURE__ */ new Date()) {
|
|
17636
|
-
const raw = `cnry_${
|
|
17637
|
-
const id =
|
|
17809
|
+
const raw = `cnry_${crypto26.randomBytes(24).toString("hex")}`;
|
|
17810
|
+
const id = crypto26.randomUUID();
|
|
17638
17811
|
db.insert(apiKeys).values({
|
|
17639
17812
|
id,
|
|
17640
17813
|
name: `${VIEWER_AERO_KEY_PREFIX}${userId}`,
|
|
@@ -17757,12 +17930,12 @@ var ViewerAeroSessions = class {
|
|
|
17757
17930
|
*/
|
|
17758
17931
|
removeKey(id) {
|
|
17759
17932
|
try {
|
|
17760
|
-
this.opts.db.delete(apiKeys).where(
|
|
17933
|
+
this.opts.db.delete(apiKeys).where(eq31(apiKeys.id, id)).run();
|
|
17761
17934
|
} catch {
|
|
17762
17935
|
try {
|
|
17763
17936
|
this.opts.db.transaction((tx) => {
|
|
17764
|
-
tx.delete(dashboardSessions).where(
|
|
17765
|
-
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();
|
|
17766
17939
|
});
|
|
17767
17940
|
} catch {
|
|
17768
17941
|
}
|
|
@@ -17775,7 +17948,7 @@ var ViewerAeroSessions = class {
|
|
|
17775
17948
|
*/
|
|
17776
17949
|
revokeOrphanedKeys() {
|
|
17777
17950
|
try {
|
|
17778
|
-
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();
|
|
17779
17952
|
} catch {
|
|
17780
17953
|
}
|
|
17781
17954
|
}
|
|
@@ -17790,9 +17963,9 @@ var ViewerAeroSessions = class {
|
|
|
17790
17963
|
};
|
|
17791
17964
|
|
|
17792
17965
|
// src/mcp-http.ts
|
|
17793
|
-
import
|
|
17966
|
+
import crypto27 from "crypto";
|
|
17794
17967
|
import { isIP } from "net";
|
|
17795
|
-
import { and as
|
|
17968
|
+
import { and as and27, eq as eq32, isNull as isNull6 } from "drizzle-orm";
|
|
17796
17969
|
import { NodeStreamableHTTPServerTransport, toNodeHandler, toWebRequest } from "@modelcontextprotocol/node";
|
|
17797
17970
|
import { McpServer, createMcpHandler, isInitializeRequest, isLegacyRequest } from "@modelcontextprotocol/server";
|
|
17798
17971
|
var SESSION_IDLE_MS = 10 * 60 * 1e3;
|
|
@@ -17801,8 +17974,8 @@ var MAX_CLEANUP_RETRIES_PER_SWEEP = 8;
|
|
|
17801
17974
|
var MAX_PENDING_KEY_RETIREMENTS = 256;
|
|
17802
17975
|
var MAX_CLEANUP_BACKOFF_MS = 10 * 60 * 1e3;
|
|
17803
17976
|
function mintSessionKey(db, scopes, userId, oauthGrantId, createdAt) {
|
|
17804
|
-
const raw = `cnry_${
|
|
17805
|
-
const id =
|
|
17977
|
+
const raw = `cnry_${crypto27.randomBytes(24).toString("hex")}`;
|
|
17978
|
+
const id = crypto27.randomUUID();
|
|
17806
17979
|
db.insert(apiKeys).values({
|
|
17807
17980
|
id,
|
|
17808
17981
|
name: `mcp-session:${userId}`,
|
|
@@ -17898,12 +18071,12 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
17898
18071
|
try {
|
|
17899
18072
|
withoutLockWait(() => {
|
|
17900
18073
|
opts.db.transaction((tx) => {
|
|
17901
|
-
tx.delete(dashboardSessions).where(
|
|
17902
|
-
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();
|
|
17903
18076
|
});
|
|
17904
18077
|
if (remove) {
|
|
17905
18078
|
try {
|
|
17906
|
-
opts.db.delete(apiKeys).where(
|
|
18079
|
+
opts.db.delete(apiKeys).where(eq32(apiKeys.id, id)).run();
|
|
17907
18080
|
} catch (error) {
|
|
17908
18081
|
scope.log.warn({ err: error, keyId: id }, "Retained revoked MCP request key after deletion failed");
|
|
17909
18082
|
}
|
|
@@ -17954,14 +18127,14 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
17954
18127
|
withoutLockWait(() => {
|
|
17955
18128
|
const cutoff = now() - SESSION_IDLE_MS;
|
|
17956
18129
|
for (const session of legacyServers) {
|
|
17957
|
-
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;
|
|
17958
18131
|
if (session.lastSeenAt < cutoff || session.expiresAt !== void 0 && session.expiresAt <= now() || session.sessionKeyId && (!key || key.revokedAt)) {
|
|
17959
18132
|
void session.close().catch((error) => scope.log.error({ err: error }, "Failed to close expired MCP session"));
|
|
17960
18133
|
}
|
|
17961
18134
|
}
|
|
17962
18135
|
for (const exchange of modernExchanges) {
|
|
17963
18136
|
if (!exchange.sessionKey) continue;
|
|
17964
|
-
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();
|
|
17965
18138
|
if (!key || key.revokedAt || exchange.sessionKey.expiresAt <= now()) {
|
|
17966
18139
|
void exchange.close().catch((error) => scope.log.error({ err: error }, "Failed to close expired MCP request"));
|
|
17967
18140
|
}
|
|
@@ -18016,7 +18189,7 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
18016
18189
|
clientName: `canonry-mcp/${PACKAGE_VERSION}`,
|
|
18017
18190
|
surface: "mcp-http",
|
|
18018
18191
|
// Correlation only: this is never accepted as caller identity or authority.
|
|
18019
|
-
...modern ? {} : { actorSession:
|
|
18192
|
+
...modern ? {} : { actorSession: crypto27.randomUUID() }
|
|
18020
18193
|
});
|
|
18021
18194
|
let server;
|
|
18022
18195
|
try {
|
|
@@ -18041,7 +18214,7 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
18041
18214
|
if (!bearer) return null;
|
|
18042
18215
|
const { server, sessionKey } = createAuthorizedServer(request, segment, bearer);
|
|
18043
18216
|
const transport = new NodeStreamableHTTPServerTransport({
|
|
18044
|
-
sessionIdGenerator: () =>
|
|
18217
|
+
sessionIdGenerator: () => crypto27.randomUUID(),
|
|
18045
18218
|
onsessioninitialized: (id) => {
|
|
18046
18219
|
if (!shuttingDown && !closing) sessions.set(id, session);
|
|
18047
18220
|
}
|
|
@@ -18211,7 +18384,7 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
18211
18384
|
}
|
|
18212
18385
|
|
|
18213
18386
|
// src/agent/agent-routes.ts
|
|
18214
|
-
import { eq as
|
|
18387
|
+
import { eq as eq33 } from "drizzle-orm";
|
|
18215
18388
|
function viewerAeroCaller(request, opts) {
|
|
18216
18389
|
if (!opts.viewerSessions) return null;
|
|
18217
18390
|
const principal = request.principal;
|
|
@@ -18251,7 +18424,7 @@ function turnAttribution(request) {
|
|
|
18251
18424
|
};
|
|
18252
18425
|
}
|
|
18253
18426
|
function resolveProject(db, name) {
|
|
18254
|
-
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();
|
|
18255
18428
|
if (!row) throw notFound("project", name);
|
|
18256
18429
|
return row;
|
|
18257
18430
|
}
|
|
@@ -18370,7 +18543,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
18370
18543
|
}
|
|
18371
18544
|
requireInstanceAdministrator(request);
|
|
18372
18545
|
const project = resolveProject(opts.db, request.params.name);
|
|
18373
|
-
const row = opts.db.select().from(agentSessions).where(
|
|
18546
|
+
const row = opts.db.select().from(agentSessions).where(eq33(agentSessions.projectId, project.id)).get();
|
|
18374
18547
|
const nextTurn = opts.sessionRegistry.nextTurnModel(project.name);
|
|
18375
18548
|
if (!row) {
|
|
18376
18549
|
return { conversationId: null, messages: [], modelProvider: null, modelId: null, nextTurn, updatedAt: null };
|
|
@@ -18412,7 +18585,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
18412
18585
|
const project = resolveProject(opts.db, request.params.name);
|
|
18413
18586
|
if (opts.sessionRegistry.isBusy(project.name)) throw agentBusy(project.name);
|
|
18414
18587
|
opts.sessionRegistry.reset(project.name);
|
|
18415
|
-
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();
|
|
18416
18589
|
return { status: "reset" };
|
|
18417
18590
|
}
|
|
18418
18591
|
);
|
|
@@ -18436,7 +18609,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
18436
18609
|
return streamViewerTurn(opts, project, viewerId, body, reply, turnAttribution(request));
|
|
18437
18610
|
}
|
|
18438
18611
|
if (body.conversationId !== void 0) {
|
|
18439
|
-
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();
|
|
18440
18613
|
if ((current?.id ?? null) !== body.conversationId) throw validationError("The active conversation changed. Reload the conversation before sending.");
|
|
18441
18614
|
}
|
|
18442
18615
|
const promptText = body.prompt;
|
|
@@ -19396,7 +19569,7 @@ function clipText(value, length) {
|
|
|
19396
19569
|
}
|
|
19397
19570
|
|
|
19398
19571
|
// src/research-runner.ts
|
|
19399
|
-
import { and as
|
|
19572
|
+
import { and as and28, eq as eq34, inArray as inArray13, sql as sql13 } from "drizzle-orm";
|
|
19400
19573
|
var unfinishedResearchQueryStatuses = [ResearchQueryStatuses.queued, ResearchQueryStatuses.running];
|
|
19401
19574
|
function finalResearchRunStatus(completed, failed, waiting) {
|
|
19402
19575
|
if (waiting > 0) return ResearchRunStatuses["waiting-upstream"];
|
|
@@ -19404,13 +19577,13 @@ function finalResearchRunStatus(completed, failed, waiting) {
|
|
|
19404
19577
|
return completed > 0 ? ResearchRunStatuses.partial : ResearchRunStatuses.failed;
|
|
19405
19578
|
}
|
|
19406
19579
|
async function executeResearchRun(db, registry, runId, projectId, options = {}) {
|
|
19407
|
-
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();
|
|
19408
19581
|
if (!run || run.status !== ResearchRunStatuses.queued) return;
|
|
19409
|
-
const project = db.select().from(projects).where(
|
|
19582
|
+
const project = db.select().from(projects).where(eq34(projects.id, projectId)).get();
|
|
19410
19583
|
if (!project) return;
|
|
19411
19584
|
const provider = registry.get(run.provider);
|
|
19412
19585
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19413
|
-
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();
|
|
19414
19587
|
if (claim.changes !== 1) return;
|
|
19415
19588
|
const elapsed = startOutcomeTimer();
|
|
19416
19589
|
const requested = currentOutcomeOrigin();
|
|
@@ -19444,8 +19617,8 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
|
|
|
19444
19617
|
}
|
|
19445
19618
|
reserved = run.totalQueries;
|
|
19446
19619
|
reservation = { scope, period };
|
|
19447
|
-
const rows = db.select().from(researchRunQueries).where(
|
|
19448
|
-
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();
|
|
19449
19622
|
const competitorDomains = competitorRows.map((row) => row.domain);
|
|
19450
19623
|
const competitorAliases = new Map(competitorRows.map((row) => [row.domain, competitorIdentityAliases(row)]).filter(([, aliases]) => aliases.length > 0));
|
|
19451
19624
|
const domains = effectiveDomains(project);
|
|
@@ -19455,7 +19628,7 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
|
|
|
19455
19628
|
await mapWithConcurrency(rows, Math.max(1, provider.config.quotaPolicy.maxConcurrency), async (row) => {
|
|
19456
19629
|
try {
|
|
19457
19630
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
19458
|
-
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();
|
|
19459
19632
|
const raw = await gate.run(async () => {
|
|
19460
19633
|
dispatched++;
|
|
19461
19634
|
return provider.adapter.executeTrackedQuery({ query: row.queryText, canonicalDomains: domains, competitorDomains, ...run.location ? { location: run.location } : {} }, config);
|
|
@@ -19486,7 +19659,7 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
|
|
|
19486
19659
|
rawResponse: raw.rawResponse,
|
|
19487
19660
|
answerEvidence: raw.evidence ?? null,
|
|
19488
19661
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19489
|
-
}).where(
|
|
19662
|
+
}).where(and28(eq34(researchRunQueries.id, row.id), eq34(researchRunQueries.status, ResearchQueryStatuses.running))).run();
|
|
19490
19663
|
if (completed.changes === 1) incrementResearchProgress(db, runId, "completedQueries");
|
|
19491
19664
|
} catch (error) {
|
|
19492
19665
|
if (!(error instanceof IncompleteAnswerError && error.resumable)) queryFailure ??= error;
|
|
@@ -19537,11 +19710,11 @@ function researchOutcomeStatus(status) {
|
|
|
19537
19710
|
}
|
|
19538
19711
|
}
|
|
19539
19712
|
function incrementResearchProgress(db, runId, column) {
|
|
19540
|
-
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();
|
|
19541
19714
|
}
|
|
19542
19715
|
function markResearchQueryFailed(db, runId, queryId, error) {
|
|
19543
19716
|
const message = describeError(error);
|
|
19544
|
-
const unfinished =
|
|
19717
|
+
const unfinished = and28(eq34(researchRunQueries.id, queryId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses));
|
|
19545
19718
|
if (error instanceof IncompleteAnswerError && error.resumable) {
|
|
19546
19719
|
db.update(researchRunQueries).set({
|
|
19547
19720
|
status: ResearchQueryStatuses["waiting-upstream"],
|
|
@@ -19557,10 +19730,10 @@ function markResearchQueryFailed(db, runId, queryId, error) {
|
|
|
19557
19730
|
if (failed.changes === 1) incrementResearchProgress(db, runId, "failedQueries");
|
|
19558
19731
|
}
|
|
19559
19732
|
function markUnfinishedResearchQueriesFailed(db, runId, error) {
|
|
19560
|
-
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();
|
|
19561
19734
|
}
|
|
19562
19735
|
function finalizeResearchRun(db, runId, fatalError) {
|
|
19563
|
-
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();
|
|
19564
19737
|
const completed = rows.filter((row) => row.status === ResearchQueryStatuses.completed).length;
|
|
19565
19738
|
const failed = rows.filter((row) => row.status === ResearchQueryStatuses.failed).length;
|
|
19566
19739
|
const waiting = rows.filter((row) => row.status === ResearchQueryStatuses["waiting-upstream"]).length;
|
|
@@ -19572,12 +19745,12 @@ function finalizeResearchRun(db, runId, fatalError) {
|
|
|
19572
19745
|
error: fatalError ?? (failed === rows.length ? "Every research query failed." : null),
|
|
19573
19746
|
// A run waiting on upstream jobs has not finished.
|
|
19574
19747
|
finishedAt: waiting > 0 ? null : (/* @__PURE__ */ new Date()).toISOString()
|
|
19575
|
-
}).where(
|
|
19748
|
+
}).where(eq34(researchRuns.id, runId)).run();
|
|
19576
19749
|
return { status, total: rows.length, completed, failed };
|
|
19577
19750
|
}
|
|
19578
19751
|
|
|
19579
19752
|
// src/google-marketing-sync.ts
|
|
19580
|
-
import { and as
|
|
19753
|
+
import { and as and29, eq as eq35, isNull as isNull7, sql as sql14 } from "drizzle-orm";
|
|
19581
19754
|
var RUNTIME_FAILURE_REASONS = {
|
|
19582
19755
|
INVALID_INPUT: OutcomeReasonCodes.VALIDATION,
|
|
19583
19756
|
NOT_CONFIGURED: OutcomeReasonCodes.NOT_CONNECTED,
|
|
@@ -19614,10 +19787,10 @@ function assertSnapshotContext(metadata, expected) {
|
|
|
19614
19787
|
}
|
|
19615
19788
|
}
|
|
19616
19789
|
function requireSyncRun(db, runId, projectId, kind) {
|
|
19617
|
-
const run = db.select().from(runs).where(
|
|
19618
|
-
|
|
19619
|
-
|
|
19620
|
-
|
|
19790
|
+
const run = db.select().from(runs).where(and29(
|
|
19791
|
+
eq35(runs.id, runId),
|
|
19792
|
+
eq35(runs.projectId, projectId),
|
|
19793
|
+
eq35(runs.kind, kind)
|
|
19621
19794
|
)).get();
|
|
19622
19795
|
if (!run) throw withOutcomeReason(new Error(`Run ${runId} is not a ${kind} run for this project.`), OutcomeReasonCodes.NOT_FOUND);
|
|
19623
19796
|
if (run.status !== RunStatuses.queued) {
|
|
@@ -19633,19 +19806,19 @@ function markFailed(db, runId, projectId, kind, expectedStatus, error) {
|
|
|
19633
19806
|
status: RunStatuses.failed,
|
|
19634
19807
|
error: serializeRunError({ message: safeErrorMessage(error) }),
|
|
19635
19808
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19636
|
-
}).where(
|
|
19637
|
-
|
|
19638
|
-
|
|
19639
|
-
|
|
19640
|
-
|
|
19809
|
+
}).where(and29(
|
|
19810
|
+
eq35(runs.id, runId),
|
|
19811
|
+
eq35(runs.projectId, projectId),
|
|
19812
|
+
eq35(runs.kind, kind),
|
|
19813
|
+
eq35(runs.status, expectedStatus)
|
|
19641
19814
|
)).run();
|
|
19642
19815
|
}
|
|
19643
19816
|
function claimSyncRun(db, runId, projectId, kind, startedAt) {
|
|
19644
|
-
const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(
|
|
19645
|
-
|
|
19646
|
-
|
|
19647
|
-
|
|
19648
|
-
|
|
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)
|
|
19649
19822
|
)).run();
|
|
19650
19823
|
return claim.changes === 1;
|
|
19651
19824
|
}
|
|
@@ -19655,9 +19828,9 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
|
|
|
19655
19828
|
let evidenceCollected = false;
|
|
19656
19829
|
try {
|
|
19657
19830
|
requireSyncRun(db, runId, projectId, RunKinds["google-ads-sync"]);
|
|
19658
|
-
const project = db.select().from(projects).where(
|
|
19831
|
+
const project = db.select().from(projects).where(eq35(projects.id, projectId)).get();
|
|
19659
19832
|
if (!project) throw withOutcomeReason(new Error(`Project ${projectId} was not found.`), OutcomeReasonCodes.NOT_FOUND);
|
|
19660
|
-
const connection = db.select().from(googleAdsConnections).where(
|
|
19833
|
+
const connection = db.select().from(googleAdsConnections).where(eq35(googleAdsConnections.projectId, projectId)).get();
|
|
19661
19834
|
if (!connection?.selectedCustomerId) {
|
|
19662
19835
|
throw withOutcomeReason(new Error("Google Ads needs a selected customer before it can sync."), OutcomeReasonCodes.ACCOUNT_NOT_FOUND);
|
|
19663
19836
|
}
|
|
@@ -19709,11 +19882,11 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
|
|
|
19709
19882
|
status: RunStatuses.completed,
|
|
19710
19883
|
finishedAt,
|
|
19711
19884
|
error: null
|
|
19712
|
-
}).where(
|
|
19713
|
-
|
|
19714
|
-
|
|
19715
|
-
|
|
19716
|
-
|
|
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)
|
|
19717
19890
|
)).run();
|
|
19718
19891
|
if (terminalClaim.changes !== 1) {
|
|
19719
19892
|
throw withOutcomeReason(
|
|
@@ -19733,12 +19906,12 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
|
|
|
19733
19906
|
lastMetricsSnapshotAt: result.metrics?.metadata.capturedAt ?? connection.lastMetricsSnapshotAt,
|
|
19734
19907
|
lastMetricsSnapshotId: result.metrics?.metadata.id ?? connection.lastMetricsSnapshotId,
|
|
19735
19908
|
updatedAt: finishedAt
|
|
19736
|
-
}).where(
|
|
19737
|
-
|
|
19738
|
-
|
|
19739
|
-
|
|
19740
|
-
connection.selectedLoginCustomerId === null ? isNull7(googleAdsConnections.selectedLoginCustomerId) :
|
|
19741
|
-
|
|
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)
|
|
19742
19915
|
)).run();
|
|
19743
19916
|
if (connectionClaim.changes !== 1) {
|
|
19744
19917
|
throw withOutcomeReason(
|
|
@@ -19778,9 +19951,9 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
19778
19951
|
let evidenceCollected = false;
|
|
19779
19952
|
try {
|
|
19780
19953
|
requireSyncRun(db, runId, projectId, RunKinds["gtm-sync"]);
|
|
19781
|
-
const project = db.select().from(projects).where(
|
|
19954
|
+
const project = db.select().from(projects).where(eq35(projects.id, projectId)).get();
|
|
19782
19955
|
if (!project) throw withOutcomeReason(new Error(`Project ${projectId} was not found.`), OutcomeReasonCodes.NOT_FOUND);
|
|
19783
|
-
const connection = db.select().from(gtmConnections).where(
|
|
19956
|
+
const connection = db.select().from(gtmConnections).where(eq35(gtmConnections.projectId, projectId)).get();
|
|
19784
19957
|
if (!connection?.selectedAccountId || !connection.selectedContainerId) {
|
|
19785
19958
|
throw withOutcomeReason(new Error("GTM needs a selected account and container before it can sync."), OutcomeReasonCodes.ACCOUNT_NOT_FOUND);
|
|
19786
19959
|
}
|
|
@@ -19818,11 +19991,11 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
19818
19991
|
status: RunStatuses.completed,
|
|
19819
19992
|
finishedAt,
|
|
19820
19993
|
error: null
|
|
19821
|
-
}).where(
|
|
19822
|
-
|
|
19823
|
-
|
|
19824
|
-
|
|
19825
|
-
|
|
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)
|
|
19826
19999
|
)).run();
|
|
19827
20000
|
if (terminalClaim.changes !== 1) {
|
|
19828
20001
|
throw withOutcomeReason(
|
|
@@ -19843,13 +20016,13 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
19843
20016
|
lastSnapshotAt: snapshot.metadata.capturedAt,
|
|
19844
20017
|
lastSnapshotId: snapshot.metadata.id,
|
|
19845
20018
|
updatedAt: finishedAt
|
|
19846
|
-
}).where(
|
|
19847
|
-
|
|
19848
|
-
|
|
19849
|
-
|
|
19850
|
-
|
|
19851
|
-
connection.selectedWorkspaceId === null ? isNull7(gtmConnections.selectedWorkspaceId) :
|
|
19852
|
-
|
|
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)
|
|
19853
20026
|
)).run();
|
|
19854
20027
|
if (connectionClaim.changes !== 1) {
|
|
19855
20028
|
throw withOutcomeReason(
|
|
@@ -20052,8 +20225,8 @@ var DASHBOARD_SCRYPT_KEYLEN = 64;
|
|
|
20052
20225
|
var DASHBOARD_SCRYPT_COST = 1 << 15;
|
|
20053
20226
|
var DASHBOARD_SCRYPT_MAXMEM = 64 * 1024 * 1024;
|
|
20054
20227
|
function hashDashboardPassword(password) {
|
|
20055
|
-
const salt =
|
|
20056
|
-
const derived =
|
|
20228
|
+
const salt = crypto28.randomBytes(16);
|
|
20229
|
+
const derived = crypto28.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
|
|
20057
20230
|
N: DASHBOARD_SCRYPT_COST,
|
|
20058
20231
|
maxmem: DASHBOARD_SCRYPT_MAXMEM
|
|
20059
20232
|
});
|
|
@@ -20074,14 +20247,14 @@ function verifyDashboardPassword(password, storedHash) {
|
|
|
20074
20247
|
} catch {
|
|
20075
20248
|
return { ok: false, needsRehash: false };
|
|
20076
20249
|
}
|
|
20077
|
-
const derived =
|
|
20250
|
+
const derived = crypto28.scryptSync(password, salt, expected.length, {
|
|
20078
20251
|
N: DASHBOARD_SCRYPT_COST,
|
|
20079
20252
|
maxmem: DASHBOARD_SCRYPT_MAXMEM
|
|
20080
20253
|
});
|
|
20081
20254
|
if (derived.length !== expected.length)
|
|
20082
20255
|
return { ok: false, needsRehash: false };
|
|
20083
20256
|
return {
|
|
20084
|
-
ok:
|
|
20257
|
+
ok: crypto28.timingSafeEqual(derived, expected),
|
|
20085
20258
|
needsRehash: false
|
|
20086
20259
|
};
|
|
20087
20260
|
}
|
|
@@ -20090,7 +20263,7 @@ function verifyDashboardPassword(password, storedHash) {
|
|
|
20090
20263
|
const expected = Buffer.from(storedHash, "hex");
|
|
20091
20264
|
if (candidate.length !== expected.length)
|
|
20092
20265
|
return { ok: false, needsRehash: false };
|
|
20093
|
-
const ok =
|
|
20266
|
+
const ok = crypto28.timingSafeEqual(candidate, expected);
|
|
20094
20267
|
return { ok, needsRehash: ok };
|
|
20095
20268
|
}
|
|
20096
20269
|
return { ok: false, needsRehash: false };
|
|
@@ -20257,7 +20430,7 @@ function cloneGtmConfig(config) {
|
|
|
20257
20430
|
function createGoogleMarketingConfigCredentialStore(input) {
|
|
20258
20431
|
const persistConfigPatch = input.saveConfigPatch ?? saveConfigPatch;
|
|
20259
20432
|
const env = input.env ?? process.env;
|
|
20260
|
-
const randomUUID2 = input.randomUUID ??
|
|
20433
|
+
const randomUUID2 = input.randomUUID ?? crypto28.randomUUID;
|
|
20261
20434
|
const snapshot = () => ({
|
|
20262
20435
|
googleAds: cloneGoogleAdsConfig(input.config.googleAds),
|
|
20263
20436
|
gtm: cloneGtmConfig(input.config.gtm)
|
|
@@ -20374,7 +20547,7 @@ async function createServer(opts) {
|
|
|
20374
20547
|
const trustProxy = resolveTrustProxy(process.env.CANONRY_TRUST_PROXY);
|
|
20375
20548
|
const app = Fastify({
|
|
20376
20549
|
loggerInstance: logger,
|
|
20377
|
-
genReqId: () =>
|
|
20550
|
+
genReqId: () => crypto28.randomUUID(),
|
|
20378
20551
|
trustProxy
|
|
20379
20552
|
});
|
|
20380
20553
|
if (!ownsDefaultConfig) {
|
|
@@ -20505,7 +20678,7 @@ async function createServer(opts) {
|
|
|
20505
20678
|
async (ctx) => {
|
|
20506
20679
|
if (!sessionRegistry) return;
|
|
20507
20680
|
if (!agentProactive) return;
|
|
20508
|
-
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();
|
|
20509
20682
|
if (!project) return;
|
|
20510
20683
|
let content;
|
|
20511
20684
|
if (ctx.kind === RunKinds["aeo-discover-probe"]) {
|
|
@@ -20979,7 +21152,7 @@ async function createServer(opts) {
|
|
|
20979
21152
|
void (async () => {
|
|
20980
21153
|
try {
|
|
20981
21154
|
const report = await schedulerClient.runDoctor({ project: projectName, checkIds: scheduledHealthCheckIds() });
|
|
20982
|
-
const project = opts.db.select().from(projects).where(
|
|
21155
|
+
const project = opts.db.select().from(projects).where(eq36(projects.name, projectName)).get();
|
|
20983
21156
|
if (!project) {
|
|
20984
21157
|
app.log.warn({ projectName }, "doctor schedule fired for an unknown project");
|
|
20985
21158
|
return;
|
|
@@ -21173,7 +21346,7 @@ async function createServer(opts) {
|
|
|
21173
21346
|
return removed;
|
|
21174
21347
|
}
|
|
21175
21348
|
};
|
|
21176
|
-
const googleStateSecret = process.env.GOOGLE_STATE_SECRET ??
|
|
21349
|
+
const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto28.randomBytes(32).toString("hex");
|
|
21177
21350
|
const googleConnectionStore = {
|
|
21178
21351
|
listConnections: (domain) => listGoogleConnections(opts.config, domain),
|
|
21179
21352
|
getConnection: (domain, connectionType) => getGoogleConnection(opts.config, domain, connectionType),
|
|
@@ -21271,11 +21444,11 @@ async function createServer(opts) {
|
|
|
21271
21444
|
})();
|
|
21272
21445
|
if (opts.config.apiKey) {
|
|
21273
21446
|
const keyHash = hashApiKey(opts.config.apiKey);
|
|
21274
|
-
const existing = opts.db.select().from(apiKeys).where(
|
|
21447
|
+
const existing = opts.db.select().from(apiKeys).where(eq36(apiKeys.keyHash, keyHash)).get();
|
|
21275
21448
|
if (!existing) {
|
|
21276
21449
|
const prefix = opts.config.apiKey.slice(0, 12);
|
|
21277
21450
|
opts.db.insert(apiKeys).values({
|
|
21278
|
-
id: `key_${
|
|
21451
|
+
id: `key_${crypto28.randomBytes(8).toString("hex")}`,
|
|
21279
21452
|
name: "default",
|
|
21280
21453
|
keyHash,
|
|
21281
21454
|
keyPrefix: prefix,
|
|
@@ -21290,14 +21463,14 @@ async function createServer(opts) {
|
|
|
21290
21463
|
);
|
|
21291
21464
|
const currentPasswordFingerprint = () => hashSessionToken(opts.config.dashboardPasswordHash ?? "");
|
|
21292
21465
|
opts.db.delete(dashboardSessions).where(or4(
|
|
21293
|
-
|
|
21294
|
-
|
|
21466
|
+
eq36(dashboardSessions.apiKeyHash, ""),
|
|
21467
|
+
and30(
|
|
21295
21468
|
isNotNull5(dashboardSessions.passwordFingerprint),
|
|
21296
21469
|
ne4(dashboardSessions.passwordFingerprint, currentPasswordFingerprint())
|
|
21297
21470
|
)
|
|
21298
21471
|
)).run();
|
|
21299
21472
|
if (!opts.config.dashboardPasswordHash) {
|
|
21300
|
-
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;
|
|
21301
21474
|
const createdAt = lastPasswordEventAt("dashboard-password.created");
|
|
21302
21475
|
const clearedAt = lastPasswordEventAt("dashboard-password.cleared");
|
|
21303
21476
|
if (createdAt !== null && (clearedAt === null || clearedAt < createdAt)) {
|
|
@@ -21312,7 +21485,7 @@ async function createServer(opts) {
|
|
|
21312
21485
|
const createSession = (key, passwordBased = false, db = opts.db) => {
|
|
21313
21486
|
const now = /* @__PURE__ */ new Date();
|
|
21314
21487
|
db.delete(dashboardSessions).where(lte3(dashboardSessions.expiresAt, now.toISOString())).run();
|
|
21315
|
-
const sessionId =
|
|
21488
|
+
const sessionId = crypto28.randomBytes(32).toString("hex");
|
|
21316
21489
|
db.insert(dashboardSessions).values({
|
|
21317
21490
|
tokenHash: hashSessionToken(sessionId),
|
|
21318
21491
|
apiKeyId: key.id,
|
|
@@ -21331,23 +21504,23 @@ async function createServer(opts) {
|
|
|
21331
21504
|
currentApiKeyHash: apiKeys.keyHash,
|
|
21332
21505
|
passwordFingerprint: dashboardSessions.passwordFingerprint,
|
|
21333
21506
|
expiresAt: dashboardSessions.expiresAt
|
|
21334
|
-
}).from(dashboardSessions).innerJoin(apiKeys,
|
|
21507
|
+
}).from(dashboardSessions).innerJoin(apiKeys, eq36(apiKeys.id, dashboardSessions.apiKeyId)).where(and30(eq36(dashboardSessions.tokenHash, tokenHash), isNull8(apiKeys.revokedAt))).get();
|
|
21335
21508
|
if (!session) return null;
|
|
21336
21509
|
const expiresAt = Date.parse(session.expiresAt);
|
|
21337
21510
|
if (!Number.isFinite(expiresAt) || expiresAt <= Date.now() || session.apiKeyHash !== session.currentApiKeyHash || session.passwordFingerprint !== null && session.passwordFingerprint !== currentPasswordFingerprint()) {
|
|
21338
|
-
opts.db.delete(dashboardSessions).where(
|
|
21511
|
+
opts.db.delete(dashboardSessions).where(eq36(dashboardSessions.tokenHash, tokenHash)).run();
|
|
21339
21512
|
return null;
|
|
21340
21513
|
}
|
|
21341
21514
|
return session.apiKeyId;
|
|
21342
21515
|
};
|
|
21343
21516
|
const clearSession = (sessionId) => {
|
|
21344
21517
|
if (sessionId) {
|
|
21345
|
-
opts.db.delete(dashboardSessions).where(
|
|
21518
|
+
opts.db.delete(dashboardSessions).where(eq36(dashboardSessions.tokenHash, hashSessionToken(sessionId))).run();
|
|
21346
21519
|
}
|
|
21347
21520
|
};
|
|
21348
21521
|
const getDefaultApiKey = () => {
|
|
21349
21522
|
if (!opts.config.apiKey) return void 0;
|
|
21350
|
-
return opts.db.select().from(apiKeys).where(
|
|
21523
|
+
return opts.db.select().from(apiKeys).where(eq36(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
|
|
21351
21524
|
};
|
|
21352
21525
|
const setSessionCookie = (reply, sessionId) => {
|
|
21353
21526
|
reply.header(
|
|
@@ -21376,7 +21549,7 @@ async function createServer(opts) {
|
|
|
21376
21549
|
if (parts.length !== 2 || parts[0] !== "Bearer" || !parts[1]) return false;
|
|
21377
21550
|
const presented = Buffer.from(hashApiKey(parts[1]), "hex");
|
|
21378
21551
|
const expected = Buffer.from(key.keyHash, "hex");
|
|
21379
|
-
return presented.length === expected.length &&
|
|
21552
|
+
return presented.length === expected.length && crypto28.timingSafeEqual(presented, expected);
|
|
21380
21553
|
};
|
|
21381
21554
|
const namedAccountsInUse = () => anyUsersExist(opts.db);
|
|
21382
21555
|
const NAMED_ACCOUNTS_MESSAGE = "This install uses named accounts. Sign in with your name and password.";
|
|
@@ -21508,12 +21681,12 @@ async function createServer(opts) {
|
|
|
21508
21681
|
return reply.send({ authenticated: true });
|
|
21509
21682
|
}
|
|
21510
21683
|
if (apiKey) {
|
|
21511
|
-
const key = opts.db.select().from(apiKeys).where(
|
|
21684
|
+
const key = opts.db.select().from(apiKeys).where(eq36(apiKeys.keyHash, hashApiKey(apiKey))).get();
|
|
21512
21685
|
if (!key || key.revokedAt) {
|
|
21513
21686
|
const err2 = authInvalid();
|
|
21514
21687
|
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
21515
21688
|
}
|
|
21516
|
-
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();
|
|
21517
21690
|
setSessionCookie(reply, createSession(key));
|
|
21518
21691
|
return reply.send({ authenticated: true });
|
|
21519
21692
|
}
|
|
@@ -21591,7 +21764,7 @@ async function createServer(opts) {
|
|
|
21591
21764
|
const recomputeDebts = /* @__PURE__ */ new Map();
|
|
21592
21765
|
let recomputeSequence = 0;
|
|
21593
21766
|
const markAnswerFieldRecompute = (projectId, full) => {
|
|
21594
|
-
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();
|
|
21595
21768
|
};
|
|
21596
21769
|
const requestAnswerFieldRecompute = (projectId, projectName, scope, reason) => {
|
|
21597
21770
|
const sequence = ++recomputeSequence;
|
|
@@ -21609,10 +21782,10 @@ async function createServer(opts) {
|
|
|
21609
21782
|
if (pass.full && debt.latestFull <= sequence) debt.latestFull = 0;
|
|
21610
21783
|
try {
|
|
21611
21784
|
if (debt.latest === sequence && debt.latestFull === 0) {
|
|
21612
|
-
opts.db.update(projects).set({ answerFieldsRecompute: null }).where(
|
|
21785
|
+
opts.db.update(projects).set({ answerFieldsRecompute: null }).where(eq36(projects.id, projectId)).run();
|
|
21613
21786
|
if (recomputeDebts.get(projectId) === debt) recomputeDebts.delete(projectId);
|
|
21614
21787
|
} else if (debt.latestFull === 0) {
|
|
21615
|
-
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();
|
|
21616
21789
|
}
|
|
21617
21790
|
} catch (err) {
|
|
21618
21791
|
app.log.error({ err, projectId }, "could not clear the owed answer field recompute");
|
|
@@ -21649,7 +21822,7 @@ async function createServer(opts) {
|
|
|
21649
21822
|
await yieldToEventLoop4();
|
|
21650
21823
|
const reason = (competitorAutoAliasReasons.get(projectId) ?? []).join(",");
|
|
21651
21824
|
competitorAutoAliasReasons.delete(projectId);
|
|
21652
|
-
const project = opts.db.select().from(projects).where(
|
|
21825
|
+
const project = opts.db.select().from(projects).where(eq36(projects.id, projectId)).get();
|
|
21653
21826
|
if (!project || project.competitorAutoAliases === "off") return;
|
|
21654
21827
|
if (project.competitorAutoAliases === "apply") {
|
|
21655
21828
|
await competitorAutoAliases.request(projectId, reason).catch(() => void 0);
|
|
@@ -21849,8 +22022,8 @@ async function createServer(opts) {
|
|
|
21849
22022
|
getAgentPin: () => describeAgentPin(opts.config),
|
|
21850
22023
|
getGoogleMarketingDoctorInput: (ctx) => {
|
|
21851
22024
|
if (!ctx.project) return null;
|
|
21852
|
-
const googleAds = opts.db.select().from(googleAdsConnections).where(
|
|
21853
|
-
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();
|
|
21854
22027
|
const googleAdsCredential = getGoogleAdsConnection(opts.config, ctx.project.id);
|
|
21855
22028
|
const gtmCredential = getGtmConnection(opts.config, ctx.project.id);
|
|
21856
22029
|
const googleAdsConnected = Boolean(googleAdsCredential?.accessToken);
|
|
@@ -21979,7 +22152,7 @@ async function createServer(opts) {
|
|
|
21979
22152
|
deps: {
|
|
21980
22153
|
enqueueAutoExtract: ({ projectId, release: r }) => {
|
|
21981
22154
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
21982
|
-
const runId =
|
|
22155
|
+
const runId = crypto28.randomUUID();
|
|
21983
22156
|
opts.db.insert(runs).values({
|
|
21984
22157
|
id: runId,
|
|
21985
22158
|
projectId,
|
|
@@ -22100,7 +22273,7 @@ async function createServer(opts) {
|
|
|
22100
22273
|
...inspectOpts,
|
|
22101
22274
|
config: opts.config
|
|
22102
22275
|
}).then(() => {
|
|
22103
|
-
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();
|
|
22104
22277
|
if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
|
|
22105
22278
|
return maybeRefreshGscCoverage(
|
|
22106
22279
|
opts.db,
|
|
@@ -22612,7 +22785,7 @@ async function createServer(opts) {
|
|
|
22612
22785
|
isBacklinksInstalled: () => isDuckdbInstalled()
|
|
22613
22786
|
});
|
|
22614
22787
|
}
|
|
22615
|
-
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()) {
|
|
22616
22789
|
dispatchResearchRun(run.id, run.projectId, OutcomeTriggers.startup);
|
|
22617
22790
|
}
|
|
22618
22791
|
runtimeStartupSettled = true;
|