@canonry/canonry 7.81.0 → 7.82.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.
Files changed (131) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +2 -0
  2. package/assets/assets/{AgentSetupPage-vJPymut8.js → AgentSetupPage-CcjQeUvK.js} +1 -1
  3. package/assets/assets/{AuditHistoryPanel-BItu4Vzu.js → AuditHistoryPanel-DgTvt2hQ.js} +1 -1
  4. package/assets/assets/{BacklinksPage-BzYbNK-V.js → BacklinksPage-DFQhtk4d.js} +1 -1
  5. package/assets/assets/{HistoryPage-B_llsE1v.js → HistoryPage-CkkWqQol.js} +1 -1
  6. package/assets/assets/{MeasurementPropertyPage-CUoV6lhB.js → MeasurementPropertyPage-Cwnda2BA.js} +1 -1
  7. package/assets/assets/{ProjectPage-C6woOQUb.js → ProjectPage-C39ua8CG.js} +1 -1
  8. package/assets/assets/{RunsPage-D_2CO_AJ.js → RunsPage-B4tMl-_9.js} +1 -1
  9. package/assets/assets/{SettingsPage-C-Ik_cYz.js → SettingsPage-DmDdf02o.js} +1 -1
  10. package/assets/assets/{SiteHealthSection-C-BOkaLy.js → SiteHealthSection-CfQePJ7s.js} +2 -2
  11. package/assets/assets/ToneBadge-bXf5LaSI.js +60 -0
  12. package/assets/assets/{TrafficPage-CZ8ylTak.js → TrafficPage-C9y8xEr6.js} +1 -1
  13. package/assets/assets/{TrafficSourceDetailPage-DFhPy-yp.js → TrafficSourceDetailPage-Dklvlek4.js} +1 -1
  14. package/assets/assets/{account-context-BN8ksOuB.js → account-context-BjM5ZlYl.js} +1 -1
  15. package/assets/assets/{card-BKhZmy5E.js → card-CDFncx20.js} +1 -1
  16. package/assets/assets/{extract-error-message-CJvSceYp.js → extract-error-message-s3FKmMjt.js} +1 -1
  17. package/assets/assets/{format-helpers-DN6eKZvA.js → format-helpers-CYQ-LjnQ.js} +1 -1
  18. package/assets/assets/{index-D-lS_O3g.js → index-qWvnlyfA.js} +4 -4
  19. package/assets/assets/{query-client-X8eRXjbT.js → query-client-C_dAEVvf.js} +1 -1
  20. package/assets/assets/{react-query-DOLt-sL-.js → react-query-BKozxbv1.js} +1 -1
  21. package/assets/assets/{react-sigma_core.esm.min-DOMHedxL.js → react-sigma_core.esm.min-J2l6CcJl.js} +1 -1
  22. package/assets/assets/{service-BiQUl6ZL.js → service-BhAtQFOI.js} +1 -1
  23. package/assets/index.html +7 -7
  24. package/dist/{ads-XDPKJZD5.js → ads-UDOONRKQ.js} +3 -3
  25. package/dist/{agent-TLMUNKYG.js → agent-JTEGEX6X.js} +7 -7
  26. package/dist/{agent-ask-J3OIR65S.js → agent-ask-PUV4EVGS.js} +3 -3
  27. package/dist/{agent-conversations-KLLYICAB.js → agent-conversations-TWTSQHBR.js} +3 -3
  28. package/dist/{agent-memory-HVT2M2AW.js → agent-memory-ZJEXAUJB.js} +3 -3
  29. package/dist/{agent-providers-QMS2NZTY.js → agent-providers-2RQZSGMP.js} +3 -3
  30. package/dist/{agent-transcript-V7UCK257.js → agent-transcript-YYS3XBES.js} +3 -3
  31. package/dist/{analytics-37Z5MGHQ.js → analytics-RLC6PRUZ.js} +3 -3
  32. package/dist/{apply-F3YZEJD4.js → apply-Z5SSCEJ2.js} +3 -3
  33. package/dist/{backfill-3YUPT5T2.js → backfill-6K2UMALK.js} +9 -9
  34. package/dist/{backlinks-GDTPG3SB.js → backlinks-LDI2VTPT.js} +3 -3
  35. package/dist/{bing-H7D5KUQF.js → bing-SSNMNZEF.js} +3 -3
  36. package/dist/{bootstrap-JLC26QRM.js → bootstrap-ISRV5I52.js} +10 -10
  37. package/dist/{cdp-WVMC4V4P.js → cdp-WU7QCPQ3.js} +7 -7
  38. package/dist/{chunk-TKOJAZ6S.js → chunk-2FIDPBWE.js} +1 -1
  39. package/dist/{chunk-AX7BBB3S.js → chunk-2GJLKCXL.js} +24 -1
  40. package/dist/{chunk-H5WAYPLS.js → chunk-2PDDTRTG.js} +4 -4
  41. package/dist/{chunk-TJA6Z5FV.js → chunk-462CIYLY.js} +1 -1
  42. package/dist/{chunk-FQYMARCU.js → chunk-4IYFVC22.js} +3 -3
  43. package/dist/{chunk-L6R6MSWW.js → chunk-5K3S74ZX.js} +1 -1
  44. package/dist/{chunk-QVMUMIG4.js → chunk-5NDUJLUL.js} +6 -6
  45. package/dist/{chunk-6ST5JIUJ.js → chunk-7ZJHKLTK.js} +1 -1
  46. package/dist/{chunk-SJYKKUH5.js → chunk-AAMOBJXJ.js} +1 -1
  47. package/dist/{chunk-KRKOTKTY.js → chunk-DWFVH3E2.js} +2 -2
  48. package/dist/{chunk-OHXAS7LT.js → chunk-EIDOBJF2.js} +2 -2
  49. package/dist/{chunk-SSTQINNT.js → chunk-F4FDJI5O.js} +3 -3
  50. package/dist/{chunk-J5K7QM3U.js → chunk-GKHM5OT6.js} +2 -2
  51. package/dist/{chunk-M6IABRSO.js → chunk-HDVJNYYE.js} +3 -3
  52. package/dist/{chunk-6T7JZS67.js → chunk-J2DA4LUR.js} +402 -270
  53. package/dist/{chunk-VSWED7DX.js → chunk-J7EYQFMJ.js} +4 -4
  54. package/dist/{chunk-EDV5YR2J.js → chunk-JDEHNF7K.js} +1 -1
  55. package/dist/{chunk-Z6HMIMK2.js → chunk-JMUSNZJR.js} +1 -1
  56. package/dist/{chunk-4EPCWJAM.js → chunk-JP4RTZGE.js} +2 -2
  57. package/dist/{chunk-2ZRCLJQX.js → chunk-LRFCNERP.js} +3 -3
  58. package/dist/{chunk-GK5H5QAH.js → chunk-M7WS2BL3.js} +1 -1
  59. package/dist/{chunk-F3EJRABZ.js → chunk-NNUZ37NJ.js} +4 -4
  60. package/dist/{chunk-IJGPHVZU.js → chunk-NPMJFPBM.js} +24 -4
  61. package/dist/{chunk-GRS5UBVQ.js → chunk-OH42CAWP.js} +1 -1
  62. package/dist/{chunk-PR2JM2FR.js → chunk-QHEKMJL4.js} +2 -2
  63. package/dist/{chunk-WR2VQ5TF.js → chunk-RMZCQUKN.js} +336 -336
  64. package/dist/{chunk-BALMAZTD.js → chunk-S6IC7ZK2.js} +9 -1
  65. package/dist/{chunk-FOQUCSNN.js → chunk-SIRWPXGJ.js} +8 -8
  66. package/dist/{chunk-YJ4EEYIP.js → chunk-SVF4P6DB.js} +1 -1
  67. package/dist/{chunk-CR6OF4SL.js → chunk-T76RQIGK.js} +3 -3
  68. package/dist/{chunk-ATOAIF2W.js → chunk-VVOCZTNC.js} +2 -2
  69. package/dist/{chunk-HPCDCZJW.js → chunk-VWYB4RA4.js} +2 -2
  70. package/dist/{chunk-FFS6J325.js → chunk-WB6VIQ7L.js} +1 -1
  71. package/dist/{chunk-Q6I2FSVG.js → chunk-ZR3RYJF3.js} +5 -5
  72. package/dist/{chunk-YJ7FKGRE.js → chunk-ZX7XDVPC.js} +2 -2
  73. package/dist/{citations-LQEXT3QX.js → citations-X5LS7SZX.js} +3 -3
  74. package/dist/{cli-catalog-FQ3GU4I7.js → cli-catalog-S4RLOOCK.js} +3 -3
  75. package/dist/{cli-commands-V5ITGQYI.js → cli-commands-BGNM6H6P.js} +7 -7
  76. package/dist/cli.js +13 -13
  77. package/dist/{competitor-LNBFBUHM.js → competitor-O5YXN5SD.js} +3 -3
  78. package/dist/{content-SXOXACBG.js → content-C6SO5LNZ.js} +3 -3
  79. package/dist/{dashboard-VQ7ZJM3Z.js → dashboard-3P6FFYCW.js} +6 -6
  80. package/dist/{demo-MVF5RBLW.js → demo-OJ6C4DU3.js} +1 -1
  81. package/dist/{demo-server-O4GV5RCH.js → demo-server-GJXDVREB.js} +12 -12
  82. package/dist/{discover-62BQKXZE.js → discover-Z355GK36.js} +3 -3
  83. package/dist/{discover-eval-6UYCPXIC.js → discover-eval-6OVS55LV.js} +3 -3
  84. package/dist/{doctor-GAD732OH.js → doctor-E52VMIEP.js} +3 -3
  85. package/dist/{evidence-HMBZTISH.js → evidence-ITVU3HYF.js} +3 -3
  86. package/dist/{export-cmd-H4QAPQLL.js → export-cmd-S2UD3ALT.js} +3 -3
  87. package/dist/{ga-EIUF4YK5.js → ga-T3UEGAZC.js} +3 -3
  88. package/dist/{gbp-IY55YRX3.js → gbp-3G45OTYX.js} +3 -3
  89. package/dist/{google-7VNVJRG5.js → google-YZS4HLN3.js} +4 -4
  90. package/dist/{google-marketing-5VRNWIYL.js → google-marketing-LIV6O63R.js} +1 -1
  91. package/dist/{health-cmd-IYULMWYP.js → health-cmd-2FC73XH6.js} +3 -3
  92. package/dist/{history-U47EQSVQ.js → history-KLRMAVLG.js} +3 -3
  93. package/dist/index.d.ts +115 -31
  94. package/dist/index.js +25 -25
  95. package/dist/{init-BB3AHJIC.js → init-CTP7OZTR.js} +12 -12
  96. package/dist/{insights-TMOIYIN2.js → insights-KTGRGFAN.js} +3 -3
  97. package/dist/intelligence-service-YX72V6NG.js +16 -0
  98. package/dist/{journeys-SBEEBTLZ.js → journeys-PF2OO5W2.js} +3 -3
  99. package/dist/{keys-B6WL56H5.js → keys-7NTJ7TFJ.js} +3 -3
  100. package/dist/{keyword-EVCBMO4R.js → keyword-C37ZTBOS.js} +3 -3
  101. package/dist/{logs-NFHUPOV6.js → logs-GU223TLX.js} +3 -3
  102. package/dist/{mcp-QJEEHLSF.js → mcp-2DZFAHOW.js} +4 -4
  103. package/dist/mcp.js +9 -9
  104. package/dist/{measurement-plan-HZHP6HMF.js → measurement-plan-ZSZUAW4Y.js} +5 -5
  105. package/dist/{notify-FKSEZ2RI.js → notify-GYSA5V67.js} +5 -4
  106. package/dist/{organic-evidence-S5MUDRRT.js → organic-evidence-2NLMSKHX.js} +3 -3
  107. package/dist/{overview-3CIM4X6O.js → overview-NL3B5XME.js} +4 -4
  108. package/dist/{project-SFZVTTFG.js → project-3THJAPRN.js} +3 -3
  109. package/dist/{query-3CDC52ST.js → query-JU5UHTGT.js} +3 -3
  110. package/dist/{research-YXNYFECM.js → research-WLS77NOR.js} +3 -3
  111. package/dist/{results-export-INLGMMB2.js → results-export-RQKUM5OA.js} +3 -3
  112. package/dist/{run-BHF4QPOI.js → run-H6BA7COC.js} +4 -4
  113. package/dist/{schedule-QVYRWV6M.js → schedule-ISTF4OHX.js} +3 -3
  114. package/dist/{search-QXWAPV4H.js → search-HIYAHDZR.js} +3 -3
  115. package/dist/{serve-Z23URYCX.js → serve-5WPYWTOF.js} +26 -26
  116. package/dist/{session-DYXVMEHP.js → session-4B5VWFCF.js} +13 -13
  117. package/dist/{settings-YMPACJEL.js → settings-ZI4UQXJO.js} +3 -3
  118. package/dist/{skills-ELKAS2QM.js → skills-LN34DUVA.js} +2 -2
  119. package/dist/{snapshot-5AFQZZCJ.js → snapshot-BDPWMMDS.js} +3 -3
  120. package/dist/{src-6NEOIOJN.js → src-UENQLEK4.js} +4 -2
  121. package/dist/{status-NCP3TWKF.js → status-6CIRB7TO.js} +4 -4
  122. package/dist/{technical-aeo-44AW2Q7L.js → technical-aeo-L435UGJL.js} +3 -3
  123. package/dist/{telemetry-3SZE36LD.js → telemetry-VIY5GILS.js} +4 -4
  124. package/dist/{tools-YLGNUOH6.js → tools-LILY27JS.js} +11 -11
  125. package/dist/{traffic-CHXA7CWW.js → traffic-KHGUZSP6.js} +5 -5
  126. package/dist/{users-HGMPXPZT.js → users-KLOJMJSB.js} +3 -3
  127. package/dist/{visibility-stats-QZXKJI5B.js → visibility-stats-OG2HEGFX.js} +3 -3
  128. package/dist/{wordpress-C3FM3ZP6.js → wordpress-NPVZAUSN.js} +3 -3
  129. package/package.json +1 -1
  130. package/assets/assets/ToneBadge-B8svjXgK.js +0 -60
  131. package/dist/intelligence-service-PJYJZPJF.js +0 -16
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  answerServiceAdapters,
3
3
  answerServiceDescriptors
4
- } from "./chunk-EDV5YR2J.js";
4
+ } from "./chunk-JDEHNF7K.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-QVMUMIG4.js";
30
+ } from "./chunk-5NDUJLUL.js";
31
31
  import {
32
32
  AeroToolProfiles,
33
33
  AeroToolScopes,
34
34
  buildAeroStateTools,
35
35
  isAeroToolProfile,
36
36
  truncateToolResult
37
- } from "./chunk-VSWED7DX.js";
37
+ } from "./chunk-J7EYQFMJ.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-YJ4EEYIP.js";
56
+ } from "./chunk-SVF4P6DB.js";
57
57
  import {
58
58
  claudeAdapter,
59
59
  embedQueries,
@@ -64,7 +64,7 @@ import {
64
64
  openaiAdapter,
65
65
  openrouterAdapter,
66
66
  perplexityAdapter
67
- } from "./chunk-J5K7QM3U.js";
67
+ } from "./chunk-GKHM5OT6.js";
68
68
  import {
69
69
  classifyUsageSurface,
70
70
  createApiUsageTelemetry,
@@ -76,7 +76,7 @@ import {
76
76
  startOutcomeTimer,
77
77
  trackFeatureCompleted,
78
78
  trackInstallState
79
- } from "./chunk-F3EJRABZ.js";
79
+ } from "./chunk-NNUZ37NJ.js";
80
80
  import {
81
81
  isLoopbackBindHost
82
82
  } from "./chunk-TSENU4GG.js";
@@ -84,7 +84,7 @@ import {
84
84
  isApiProviderRegistrable,
85
85
  isCdpProviderRegistrable,
86
86
  registeredProviderNames
87
- } from "./chunk-Z6HMIMK2.js";
87
+ } from "./chunk-JMUSNZJR.js";
88
88
  import {
89
89
  checkLatestVersionForServer,
90
90
  getOrCreateAnonymousId,
@@ -96,17 +96,17 @@ import {
96
96
  recordDashboardEvent,
97
97
  setTelemetryPreference,
98
98
  trackEvent
99
- } from "./chunk-HPCDCZJW.js";
99
+ } from "./chunk-VWYB4RA4.js";
100
100
  import {
101
101
  createCanonryMcpServer
102
- } from "./chunk-CR6OF4SL.js";
102
+ } from "./chunk-T76RQIGK.js";
103
103
  import {
104
104
  CANONRY_MCP_TIERS,
105
105
  CANONRY_MCP_TOOLKIT_NAMES
106
106
  } from "./chunk-JI7FDO7I.js";
107
107
  import {
108
108
  ApiClient
109
- } from "./chunk-YJ7FKGRE.js";
109
+ } from "./chunk-ZX7XDVPC.js";
110
110
  import {
111
111
  AGENT_PROVIDERS,
112
112
  adapterSupportsBatch,
@@ -138,21 +138,21 @@ import {
138
138
  saveConfigPatch,
139
139
  sentimentInstallReadiness,
140
140
  updateSharedProviderExecutionGate
141
- } from "./chunk-6ST5JIUJ.js";
141
+ } from "./chunk-7ZJHKLTK.js";
142
142
  import {
143
143
  getBundledSkillSnapshots
144
- } from "./chunk-TJA6Z5FV.js";
144
+ } from "./chunk-462CIYLY.js";
145
145
  import {
146
146
  computeFactorAverages,
147
147
  deleteSiteCrawlGraphLayout,
148
148
  persistSiteCrawlGraphLayout,
149
149
  prepareSiteCrawlGraphLayout,
150
150
  recordedSharePct
151
- } from "./chunk-KRKOTKTY.js";
151
+ } from "./chunk-DWFVH3E2.js";
152
152
  import {
153
153
  CanonryMcpToolNames,
154
154
  canonryMcpTools
155
- } from "./chunk-M6IABRSO.js";
155
+ } from "./chunk-HDVJNYYE.js";
156
156
  import {
157
157
  PACKAGE_VERSION
158
158
  } from "./chunk-AOJU5LKX.js";
@@ -161,16 +161,16 @@ import {
161
161
  } from "./chunk-HGMYEDAQ.js";
162
162
  import {
163
163
  IntelligenceService
164
- } from "./chunk-Q6I2FSVG.js";
164
+ } from "./chunk-ZR3RYJF3.js";
165
165
  import {
166
166
  addLogListener,
167
167
  createFastifyLogger,
168
168
  createLogger
169
- } from "./chunk-ATOAIF2W.js";
169
+ } from "./chunk-VVOCZTNC.js";
170
170
  import {
171
171
  computeCitedCompetitorDomains,
172
172
  determineCitationState
173
- } from "./chunk-GRS5UBVQ.js";
173
+ } from "./chunk-OH42CAWP.js";
174
174
  import {
175
175
  BingApiError,
176
176
  CC_CACHE_DIR,
@@ -291,7 +291,7 @@ import {
291
291
  webhookResponseReason,
292
292
  webhookTargetRefusalReason,
293
293
  writeAuditLog
294
- } from "./chunk-IJGPHVZU.js";
294
+ } from "./chunk-NPMJFPBM.js";
295
295
  import {
296
296
  OperationalLogStore,
297
297
  SentimentIdempotencyConflict,
@@ -376,8 +376,10 @@ import {
376
376
  siteCrawlSnapshots,
377
377
  siteLivenessState,
378
378
  trafficSources,
379
+ unrecognizedAgentEventsHourly,
380
+ unrecognizedAgentNotifyState,
379
381
  usageCounters
380
- } from "./chunk-AX7BBB3S.js";
382
+ } from "./chunk-2GJLKCXL.js";
381
383
  import {
382
384
  AGENT_MEMORY_VALUE_MAX_BYTES,
383
385
  AI_ENGINE_DOMAINS,
@@ -633,7 +635,7 @@ import {
633
635
  winnabilityClassLabel,
634
636
  withRetry,
635
637
  withSkippedProviders
636
- } from "./chunk-BALMAZTD.js";
638
+ } from "./chunk-S6IC7ZK2.js";
637
639
 
638
640
  // src/google-marketing-runtime.ts
639
641
  import crypto2 from "crypto";
@@ -3323,7 +3325,7 @@ import path5 from "path";
3323
3325
  import { isIP as isIP2 } from "net";
3324
3326
  import { fileURLToPath } from "url";
3325
3327
  import { setImmediate as yieldToEventLoop4 } from "timers/promises";
3326
- import { and as and27, eq as eq33, isNotNull as isNotNull5, isNull as isNull8, lte as lte3, max, ne as ne4, or as or4, sql as sql13 } from "drizzle-orm";
3328
+ import { and as and28, eq as eq34, isNotNull as isNotNull5, isNull as isNull8, lte as lte3, max, ne as ne4, or as or4, sql as sql14 } from "drizzle-orm";
3327
3329
 
3328
3330
  // src/seo-runtime.ts
3329
3331
  import { eq, inArray } from "drizzle-orm";
@@ -5937,7 +5939,7 @@ function nonBlank(value) {
5937
5939
  return trimmed ? trimmed : void 0;
5938
5940
  }
5939
5941
  function resolveBuildCommit(env = process.env) {
5940
- const embedded = true ? "a4ed15d8219d2d213822af84a858373c4f56fa78" : void 0;
5942
+ const embedded = true ? "2d31f874106c0d9772c47b19f7d79f2257976fef" : void 0;
5941
5943
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
5942
5944
  }
5943
5945
  function resolveInstanceIdentity(env = process.env) {
@@ -14297,9 +14299,84 @@ async function deliverReviewEvent(options) {
14297
14299
  return sent;
14298
14300
  }
14299
14301
 
14302
+ // src/unrecognized-agent-alerts.ts
14303
+ import { and as and19, desc as desc8, eq as eq22, gte as gte3, sql as sql9 } from "drizzle-orm";
14304
+ var log20 = createLogger("UnrecognizedAgentAlerts");
14305
+ var UNRECOGNIZED_AGENT_ALERT_WINDOW_MS = 7 * 864e5;
14306
+ var UNRECOGNIZED_AGENT_ALERT_MIN_HITS = 3;
14307
+ var UNRECOGNIZED_AGENT_ALERT_INTERVAL_MS = 15 * 6e4;
14308
+ var UnrecognizedAgentNotifyStates = {
14309
+ sent: "sent",
14310
+ skipped: "skipped",
14311
+ stale: "stale",
14312
+ baseline: "baseline"
14313
+ };
14314
+ function projectsWithRecentUnrecognizedAgents(db, now) {
14315
+ const since = new Date(now - UNRECOGNIZED_AGENT_ALERT_WINDOW_MS).toISOString();
14316
+ return db.selectDistinct({ projectId: unrecognizedAgentEventsHourly.projectId }).from(unrecognizedAgentEventsHourly).where(gte3(unrecognizedAgentEventsHourly.tsHour, since)).all().map((row) => row.projectId);
14317
+ }
14318
+ function undecidedUnrecognizedAgents(db, projectId, now) {
14319
+ const decided = new Set(db.select({ agentName: unrecognizedAgentNotifyState.agentName }).from(unrecognizedAgentNotifyState).where(eq22(unrecognizedAgentNotifyState.projectId, projectId)).all().map((row) => row.agentName));
14320
+ const totals = db.select({
14321
+ name: unrecognizedAgentEventsHourly.agentName,
14322
+ hits: sql9`SUM(${unrecognizedAgentEventsHourly.hits})`,
14323
+ firstSeenAt: sql9`MIN(${unrecognizedAgentEventsHourly.tsHour})`,
14324
+ lastSeenAt: sql9`MAX(${unrecognizedAgentEventsHourly.tsHour})`
14325
+ }).from(unrecognizedAgentEventsHourly).where(eq22(unrecognizedAgentEventsHourly.projectId, projectId)).groupBy(unrecognizedAgentEventsHourly.agentName).all();
14326
+ const windowStart = new Date(now - UNRECOGNIZED_AGENT_ALERT_WINDOW_MS).toISOString();
14327
+ const recordingStart = totals.reduce((earliest, total) => earliest === null || total.firstSeenAt < earliest ? total.firstSeenAt : earliest, null);
14328
+ const baselineEnd = recordingStart === null ? null : new Date(Date.parse(recordingStart) + UNRECOGNIZED_AGENT_ALERT_WINDOW_MS).toISOString();
14329
+ const due = [];
14330
+ const stale = [];
14331
+ const baseline = [];
14332
+ for (const total of totals) {
14333
+ if (decided.has(total.name)) continue;
14334
+ if (total.firstSeenAt < windowStart) {
14335
+ stale.push(total.name);
14336
+ continue;
14337
+ }
14338
+ if (baselineEnd !== null && total.firstSeenAt < baselineEnd) {
14339
+ baseline.push(total.name);
14340
+ continue;
14341
+ }
14342
+ if (Number(total.hits) < UNRECOGNIZED_AGENT_ALERT_MIN_HITS) continue;
14343
+ const latest = db.select({ sampledUserAgent: unrecognizedAgentEventsHourly.sampledUserAgent, sampledPath: unrecognizedAgentEventsHourly.sampledPath }).from(unrecognizedAgentEventsHourly).where(and19(eq22(unrecognizedAgentEventsHourly.projectId, projectId), eq22(unrecognizedAgentEventsHourly.agentName, total.name))).orderBy(desc8(unrecognizedAgentEventsHourly.tsHour)).limit(1).get();
14344
+ due.push({
14345
+ name: total.name,
14346
+ hits: Number(total.hits),
14347
+ firstSeenAt: total.firstSeenAt,
14348
+ lastSeenAt: total.lastSeenAt,
14349
+ sampleUserAgent: latest?.sampledUserAgent ?? null,
14350
+ samplePath: latest?.sampledPath ?? null
14351
+ });
14352
+ }
14353
+ due.sort((a, b) => b.hits - a.hits || a.name.localeCompare(b.name));
14354
+ return { due, stale, baseline };
14355
+ }
14356
+ function recordUnrecognizedAgentDecisions(db, projectId, names, state, now) {
14357
+ const recordedAt = new Date(now).toISOString();
14358
+ for (const agentName of names) {
14359
+ db.insert(unrecognizedAgentNotifyState).values({ projectId, agentName, state, recordedAt }).onConflictDoNothing().run();
14360
+ }
14361
+ }
14362
+ function startUnrecognizedAgentAlertLoop(dispatch, intervalMs = UNRECOGNIZED_AGENT_ALERT_INTERVAL_MS) {
14363
+ let running = false;
14364
+ const timer = setInterval(() => {
14365
+ if (running) return;
14366
+ running = true;
14367
+ void dispatch().catch((err) => {
14368
+ log20.warn("unrecognized-agent-alerts.dispatch-failed", { error: describeError(err) });
14369
+ }).finally(() => {
14370
+ running = false;
14371
+ });
14372
+ }, intervalMs);
14373
+ timer.unref?.();
14374
+ return () => clearInterval(timer);
14375
+ }
14376
+
14300
14377
  // src/provider-batch-poller.ts
14301
- import { and as and19, eq as eq22, inArray as inArray11, isNotNull as isNotNull3, isNull as isNull4, notExists as notExists2, sql as sql9 } from "drizzle-orm";
14302
- var log20 = createLogger("ProviderBatchPoller");
14378
+ import { and as and20, eq as eq23, inArray as inArray11, isNotNull as isNotNull3, isNull as isNull4, notExists as notExists2, sql as sql10 } from "drizzle-orm";
14379
+ var log21 = createLogger("ProviderBatchPoller");
14303
14380
  var PROVIDER_BATCH_POLL_INITIAL_MS = 3e4;
14304
14381
  var PROVIDER_BATCH_POLL_MAX_MS = 10 * 6e4;
14305
14382
  var PROVIDER_BATCH_CANCEL_GRACE_MS = 60 * 6e4;
@@ -14323,7 +14400,7 @@ var ProviderBatchPoller = class {
14323
14400
  try {
14324
14401
  if (await this.advance(row)) settledRuns.set(row.runId, row.projectId);
14325
14402
  } catch (err) {
14326
- log20.warn("batch.advance-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error: describeError(err) });
14403
+ log21.warn("batch.advance-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error: describeError(err) });
14327
14404
  this.reschedule(row.id, null);
14328
14405
  }
14329
14406
  }
@@ -14332,7 +14409,7 @@ var ProviderBatchPoller = class {
14332
14409
  try {
14333
14410
  this.deps.runner.finalizeBatchRun(runId, projectId);
14334
14411
  } catch (err) {
14335
- log20.warn("batch.finalize-failed", { runId, error: describeError(err) });
14412
+ log21.warn("batch.finalize-failed", { runId, error: describeError(err) });
14336
14413
  }
14337
14414
  }
14338
14415
  }
@@ -14341,7 +14418,7 @@ var ProviderBatchPoller = class {
14341
14418
  * outstanding (ingested or cancelled), so its run may now finalize.
14342
14419
  */
14343
14420
  async advance(row) {
14344
- const runStatus = this.deps.db.select({ status: runs.status }).from(runs).where(eq22(runs.id, row.runId)).get()?.status;
14421
+ const runStatus = this.deps.db.select({ status: runs.status }).from(runs).where(eq23(runs.id, row.runId)).get()?.status;
14345
14422
  if (runStatus !== RunStatuses.running) {
14346
14423
  await this.deps.runner.abandonProviderBatches([row.id], "Cancelled because its run is no longer running.");
14347
14424
  this.schedule.delete(row.id);
@@ -14358,15 +14435,15 @@ var ProviderBatchPoller = class {
14358
14435
  status = poll.status;
14359
14436
  if (poll.status === "ended") {
14360
14437
  const endedAt = poll.endedAt ?? new Date(this.now()).toISOString();
14361
- const ended = this.deps.db.update(providerBatches).set({ status: ProviderBatchStatuses.ended, endedAt, resultsExpireAt: poll.resultsExpireAt ?? null, updatedAt: endedAt }).where(and19(eq22(providerBatches.id, row.id), eq22(providerBatches.status, ProviderBatchStatuses.submitted))).run().changes === 1;
14438
+ const ended = this.deps.db.update(providerBatches).set({ status: ProviderBatchStatuses.ended, endedAt, resultsExpireAt: poll.resultsExpireAt ?? null, updatedAt: endedAt }).where(and20(eq23(providerBatches.id, row.id), eq23(providerBatches.status, ProviderBatchStatuses.submitted))).run().changes === 1;
14362
14439
  this.schedule.delete(row.id);
14363
14440
  return ended ? this.ingest({ ...row, status: ProviderBatchStatuses.ended, resultsExpireAt: poll.resultsExpireAt ?? null }) : false;
14364
14441
  }
14365
14442
  } catch (err) {
14366
- log20.warn("batch.poll-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error: describeError(err) });
14443
+ log21.warn("batch.poll-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error: describeError(err) });
14367
14444
  }
14368
14445
  } else {
14369
- log20.warn("batch.poll-unavailable", { batchId: row.id, runId: row.runId, provider: row.provider });
14446
+ log21.warn("batch.poll-unavailable", { batchId: row.id, runId: row.runId, provider: row.provider });
14370
14447
  }
14371
14448
  this.reschedule(row.id, status);
14372
14449
  return this.enforceDeadline(row, registered);
@@ -14382,18 +14459,18 @@ var ProviderBatchPoller = class {
14382
14459
  const at = new Date(now).toISOString();
14383
14460
  if (row.cancelRequestedAt === null) {
14384
14461
  if (now < Date.parse(row.deadlineAt)) return false;
14385
- const claimed = this.deps.db.update(providerBatches).set({ cancelRequestedAt: at, updatedAt: at }).where(and19(
14386
- eq22(providerBatches.id, row.id),
14387
- eq22(providerBatches.status, ProviderBatchStatuses.submitted),
14462
+ const claimed = this.deps.db.update(providerBatches).set({ cancelRequestedAt: at, updatedAt: at }).where(and20(
14463
+ eq23(providerBatches.id, row.id),
14464
+ eq23(providerBatches.status, ProviderBatchStatuses.submitted),
14388
14465
  isNull4(providerBatches.cancelRequestedAt)
14389
14466
  )).run().changes === 1;
14390
14467
  if (!claimed) return false;
14391
- log20.warn("batch.deadline-passed", { batchId: row.id, runId: row.runId, provider: row.provider, deadlineAt: row.deadlineAt });
14468
+ log21.warn("batch.deadline-passed", { batchId: row.id, runId: row.runId, provider: row.provider, deadlineAt: row.deadlineAt });
14392
14469
  if (registered?.adapter.batch && row.providerBatchId) {
14393
14470
  try {
14394
14471
  await registered.adapter.batch.cancel(row.providerBatchId, registered.config);
14395
14472
  } catch (err) {
14396
- log20.warn("batch.cancel-failed", { batchId: row.id, provider: row.provider, error: describeError(err) });
14473
+ log21.warn("batch.cancel-failed", { batchId: row.id, provider: row.provider, error: describeError(err) });
14397
14474
  }
14398
14475
  }
14399
14476
  return false;
@@ -14403,9 +14480,9 @@ var ProviderBatchPoller = class {
14403
14480
  status: ProviderBatchStatuses.cancelled,
14404
14481
  error: `The provider batch did not end within an hour of being cancelled at its deadline; its ${row.requestCount} answer(s) were not recorded.`,
14405
14482
  updatedAt: at
14406
- }).where(and19(eq22(providerBatches.id, row.id), eq22(providerBatches.status, ProviderBatchStatuses.submitted))).run().changes === 1;
14483
+ }).where(and20(eq23(providerBatches.id, row.id), eq23(providerBatches.status, ProviderBatchStatuses.submitted))).run().changes === 1;
14407
14484
  this.schedule.delete(row.id);
14408
- if (gaveUp) log20.warn("batch.abandoned-after-deadline", { batchId: row.id, runId: row.runId, provider: row.provider });
14485
+ if (gaveUp) log21.warn("batch.abandoned-after-deadline", { batchId: row.id, runId: row.runId, provider: row.provider });
14409
14486
  return gaveUp;
14410
14487
  }
14411
14488
  async ingest(row) {
@@ -14414,7 +14491,7 @@ var ProviderBatchPoller = class {
14414
14491
  return result.kind !== "skipped";
14415
14492
  } catch (err) {
14416
14493
  const error = describeError(err);
14417
- log20.warn("batch.ingest-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error });
14494
+ log21.warn("batch.ingest-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error });
14418
14495
  const now = this.now();
14419
14496
  const unreadable = row.resultsExpireAt !== null && now >= Date.parse(row.resultsExpireAt);
14420
14497
  if (unreadable || now >= Date.parse(row.deadlineAt) + PROVIDER_BATCH_CANCEL_GRACE_MS) {
@@ -14443,10 +14520,10 @@ var ProviderBatchPoller = class {
14443
14520
  /** Runs the sweep handed off whose batches have all settled. */
14444
14521
  finalizableRuns() {
14445
14522
  const holding = [ProviderBatchStatuses.submitting, ...OUTSTANDING_PROVIDER_BATCH_STATUSES];
14446
- return this.deps.db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and19(
14447
- eq22(runs.status, RunStatuses.running),
14523
+ return this.deps.db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and20(
14524
+ eq23(runs.status, RunStatuses.running),
14448
14525
  isNotNull3(runs.pendingProviderErrors),
14449
- notExists2(this.deps.db.select({ one: sql9`1` }).from(providerBatches).where(and19(eq22(providerBatches.runId, runs.id), inArray11(providerBatches.status, holding))))
14526
+ notExists2(this.deps.db.select({ one: sql10`1` }).from(providerBatches).where(and20(eq23(providerBatches.runId, runs.id), inArray11(providerBatches.status, holding))))
14450
14527
  )).all();
14451
14528
  }
14452
14529
  };
@@ -14456,7 +14533,7 @@ function startProviderBatchPoller(deps, intervalMs = PROVIDER_BATCH_TICK_MS) {
14456
14533
  const timer = setInterval(() => {
14457
14534
  if (running) return;
14458
14535
  running = true;
14459
- void poller.tick().catch((err) => log20.warn("batch.pass-failed", { error: describeError(err) })).finally(() => {
14536
+ void poller.tick().catch((err) => log21.warn("batch.pass-failed", { error: describeError(err) })).finally(() => {
14460
14537
  running = false;
14461
14538
  });
14462
14539
  }, intervalMs);
@@ -14465,7 +14542,7 @@ function startProviderBatchPoller(deps, intervalMs = PROVIDER_BATCH_TICK_MS) {
14465
14542
  }
14466
14543
 
14467
14544
  // src/data-refresh.ts
14468
- var log21 = createLogger("DataRefresh");
14545
+ var log22 = createLogger("DataRefresh");
14469
14546
  function gaRefreshDays(now = /* @__PURE__ */ new Date()) {
14470
14547
  const [reportMonth] = reportMonthsForDoctor(void 0, now);
14471
14548
  const reach = inclusiveDayCount(`${reportMonth}-01`, now.toISOString().slice(0, 10)) ?? 0;
@@ -14506,15 +14583,15 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
14506
14583
  if (result.status === "rejected") {
14507
14584
  const reason = result.reason;
14508
14585
  const message = describeError(reason);
14509
- log21.warn("integration.refresh-failed", { projectName, integration, error: message });
14586
+ log22.warn("integration.refresh-failed", { projectName, integration, error: message });
14510
14587
  return;
14511
14588
  }
14512
14589
  switch (completion) {
14513
14590
  case "synchronous":
14514
- log21.info("integration.refreshed", { projectName, integration });
14591
+ log22.info("integration.refreshed", { projectName, integration });
14515
14592
  return;
14516
14593
  case "queued":
14517
- log21.info("integration.queued", { projectName, integration, ...queuedRun(result.value) });
14594
+ log22.info("integration.queued", { projectName, integration, ...queuedRun(result.value) });
14518
14595
  return;
14519
14596
  default: {
14520
14597
  const _exhaustive = completion;
@@ -14525,9 +14602,9 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
14525
14602
  }
14526
14603
 
14527
14604
  // src/notifier.ts
14528
- import { eq as eq23, desc as desc8, and as and20, inArray as inArray12, or as or2, isNull as isNull5 } from "drizzle-orm";
14605
+ import { eq as eq24, desc as desc9, and as and21, inArray as inArray12, or as or2, isNull as isNull5 } from "drizzle-orm";
14529
14606
  import crypto21 from "crypto";
14530
- var log22 = createLogger("Notifier");
14607
+ var log23 = createLogger("Notifier");
14531
14608
  var sampleDelivery = createOutcomeSampler({ burst: 20, refillMs: 6e4, now: () => Date.now() });
14532
14609
  function trackWebhookDelivery(input) {
14533
14610
  const sample = sampleDelivery(input.failure ? "failed" : "succeeded");
@@ -14638,6 +14715,8 @@ var Notifier = class {
14638
14715
  allowLoopbackWebhooks;
14639
14716
  /** Projects with a review dispatch in progress; see {@link dispatchReviewAlerts}. */
14640
14717
  reviewDispatchInFlight = /* @__PURE__ */ new Set();
14718
+ /** Set while a {@link dispatchUnrecognizedAgentAlerts} pass runs, so passes never overlap. */
14719
+ unrecognizedAgentDispatchInFlight = false;
14641
14720
  constructor(db, serverUrl, opts = {}) {
14642
14721
  this.db = db;
14643
14722
  this.serverUrl = serverUrl;
@@ -14645,26 +14724,26 @@ var Notifier = class {
14645
14724
  }
14646
14725
  /** Called after a run completes (success, partial, or failed). */
14647
14726
  async onRunCompleted(runId, projectId, opts) {
14648
- log22.info("run.completed", { runId, projectId, origin: opts?.origin ?? "sweep" });
14649
- const notifs = this.db.select().from(notifications).where(eq23(notifications.projectId, projectId)).all().filter((n) => n.enabled);
14727
+ log23.info("run.completed", { runId, projectId, origin: opts?.origin ?? "sweep" });
14728
+ const notifs = this.db.select().from(notifications).where(eq24(notifications.projectId, projectId)).all().filter((n) => n.enabled);
14650
14729
  if (notifs.length === 0) {
14651
- log22.info("notifications.none-enabled", { projectId });
14730
+ log23.info("notifications.none-enabled", { projectId });
14652
14731
  return;
14653
14732
  }
14654
- log22.info("notifications.found", { projectId, count: notifs.length });
14655
- const run = this.db.select().from(runs).where(eq23(runs.id, runId)).get();
14733
+ log23.info("notifications.found", { projectId, count: notifs.length });
14734
+ const run = this.db.select().from(runs).where(eq24(runs.id, runId)).get();
14656
14735
  if (!run) {
14657
- log22.error("run.not-found", { runId, msg: "skipping notification dispatch" });
14736
+ log23.error("run.not-found", { runId, msg: "skipping notification dispatch" });
14658
14737
  return;
14659
14738
  }
14660
- const project = this.db.select().from(projects).where(eq23(projects.id, projectId)).get();
14739
+ const project = this.db.select().from(projects).where(eq24(projects.id, projectId)).get();
14661
14740
  if (!project) {
14662
- log22.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
14741
+ log23.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
14663
14742
  return;
14664
14743
  }
14665
14744
  const transitions = this.computeTransitions(runId, projectId);
14666
14745
  const events = [];
14667
- log22.info("run.status", { runId: run.id, status: run.status, projectId });
14746
+ log23.info("run.status", { runId: run.id, status: run.status, projectId });
14668
14747
  if ((run.status === "completed" || run.status === "partial") && opts?.origin !== "fill") {
14669
14748
  events.push("run.completed");
14670
14749
  }
@@ -14680,7 +14759,7 @@ var Notifier = class {
14680
14759
  if (!config.url) continue;
14681
14760
  const subscribedEvents = config.events;
14682
14761
  const matchingEvents = events.filter((e) => subscribedEvents.includes(e));
14683
- log22.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
14762
+ log23.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
14684
14763
  if (matchingEvents.length === 0) continue;
14685
14764
  for (const event of matchingEvents) {
14686
14765
  const relevantTransitions = event === "citation.lost" ? lostTransitions : event === "citation.gained" ? gainedTransitions : transitions;
@@ -14736,9 +14815,9 @@ var Notifier = class {
14736
14815
  * than inferred from the absence of a webhook.
14737
14816
  */
14738
14817
  async onHealthChecked(projectId, report) {
14739
- const project = this.db.select().from(projects).where(eq23(projects.id, projectId)).get();
14818
+ const project = this.db.select().from(projects).where(eq24(projects.id, projectId)).get();
14740
14819
  if (!project) {
14741
- log22.error("project.not-found", { projectId, msg: "skipping health notification" });
14820
+ log23.error("project.not-found", { projectId, msg: "skipping health notification" });
14742
14821
  return null;
14743
14822
  }
14744
14823
  const rank = healthRank;
@@ -14769,7 +14848,7 @@ var Notifier = class {
14769
14848
  const status = noSignal ? "warn" : worst ? worst.status : "ok";
14770
14849
  const code = noSignal ? HEALTH_NO_SIGNAL_CODE : worst?.code ?? "health.ok";
14771
14850
  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.`;
14772
- const previous = this.db.select().from(doctorHealthState).where(eq23(doctorHealthState.projectId, projectId)).get();
14851
+ const previous = this.db.select().from(doctorHealthState).where(eq24(doctorHealthState.projectId, projectId)).get();
14773
14852
  const previousStatus = previous?.status ?? null;
14774
14853
  const failingSignature = failing.map((c) => `${c.status}:${c.code}`).join(",");
14775
14854
  const failingNow = failing.map((c) => ({ id: c.id, code: c.code, rank: rank(c.status) }));
@@ -14839,7 +14918,7 @@ var Notifier = class {
14839
14918
  const urgentById = new Map(urgentItems.map((item) => [item.id, item]));
14840
14919
  const generalEvent = event;
14841
14920
  const urgentRecord = previous?.urgentNotified ?? null;
14842
- const enabled = generalEvent || urgentItems.length > 0 || urgentRecord ? this.db.select().from(notifications).where(eq23(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
14921
+ const enabled = generalEvent || urgentItems.length > 0 || urgentRecord ? this.db.select().from(notifications).where(eq24(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
14843
14922
  const listedEvents = (notif) => notif.config.events;
14844
14923
  const hasUrl = (notif) => Boolean(notif.config.url);
14845
14924
  const urgentSubscribers = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
@@ -14887,7 +14966,7 @@ var Notifier = class {
14887
14966
  if (previous === void 0) {
14888
14967
  this.db.insert(doctorHealthState).values({ ...observation, notifiedAt: null }).run();
14889
14968
  } else {
14890
- this.db.update(doctorHealthState).set(observation).where(eq23(doctorHealthState.projectId, projectId)).run();
14969
+ this.db.update(doctorHealthState).set(observation).where(eq24(doctorHealthState.projectId, projectId)).run();
14891
14970
  }
14892
14971
  for (const group of urgentGroups) {
14893
14972
  const { urgentEvent, urgentTold, urgentNew, urgentCleared, urgentNotifs, urgentSettled } = group;
@@ -14931,10 +15010,10 @@ var Notifier = class {
14931
15010
  }
14932
15011
  if (accepted > 0) {
14933
15012
  recordUrgentReceipts(urgentReceipts, group.subscribers, formatUrgentBreaches(urgentSettled) || null);
14934
- this.db.update(doctorHealthState).set({ urgentNotified: doctorUrgentSummary(urgentReceipts), urgentReceipts }).where(eq23(doctorHealthState.projectId, projectId)).run();
15013
+ this.db.update(doctorHealthState).set({ urgentNotified: doctorUrgentSummary(urgentReceipts), urgentReceipts }).where(eq24(doctorHealthState.projectId, projectId)).run();
14935
15014
  }
14936
15015
  if (urgentNotifs.length > 0) {
14937
- log22.info("health.urgent-notified", {
15016
+ log23.info("health.urgent-notified", {
14938
15017
  projectId,
14939
15018
  event: urgentEvent,
14940
15019
  codes: urgentEvent === "health.urgent" ? breachCodes(urgentNew) : breachCodes(urgentCleared),
@@ -14945,9 +15024,9 @@ var Notifier = class {
14945
15024
  }
14946
15025
  if (!event) {
14947
15026
  if (clearedThisPass.length > 0 || easedThisPass.length > 0) {
14948
- log22.info("health.improved", { projectId, status, code, cleared: clearedThisPass, eased: easedThisPass });
15027
+ log23.info("health.improved", { projectId, status, code, cleared: clearedThisPass, eased: easedThisPass });
14949
15028
  } else {
14950
- log22.info("health.unchanged", { projectId, status, code });
15029
+ log23.info("health.unchanged", { projectId, status, code });
14951
15030
  }
14952
15031
  return null;
14953
15032
  }
@@ -14975,9 +15054,9 @@ var Notifier = class {
14975
15054
  if (await this.sendWebhook(config.url, payload, notif.id, projectId, notif.webhookSecret ?? null)) delivered += 1;
14976
15055
  }
14977
15056
  if (delivered > 0) {
14978
- this.db.update(doctorHealthState).set({ notifiedAt: now, notifiedSignature: formatBreaches(failingNow), pendingSignature: null }).where(eq23(doctorHealthState.projectId, projectId)).run();
15057
+ this.db.update(doctorHealthState).set({ notifiedAt: now, notifiedSignature: formatBreaches(failingNow), pendingSignature: null }).where(eq24(doctorHealthState.projectId, projectId)).run();
14979
15058
  }
14980
- log22.info("health.notified", {
15059
+ log23.info("health.notified", {
14981
15060
  projectId,
14982
15061
  event,
14983
15062
  status,
@@ -15005,12 +15084,12 @@ var Notifier = class {
15005
15084
  async onSiteLivenessChecked(projectId, result) {
15006
15085
  const { check, checkedAt } = result;
15007
15086
  if (check.status !== "ok" && check.status !== "fail") return null;
15008
- const project = this.db.select().from(projects).where(eq23(projects.id, projectId)).get();
15087
+ const project = this.db.select().from(projects).where(eq24(projects.id, projectId)).get();
15009
15088
  if (!project) {
15010
- log22.error("project.not-found", { projectId, msg: "skipping site liveness notification" });
15089
+ log23.error("project.not-found", { projectId, msg: "skipping site liveness notification" });
15011
15090
  return null;
15012
15091
  }
15013
- const previous = this.db.select().from(siteLivenessState).where(eq23(siteLivenessState.projectId, projectId)).get();
15092
+ const previous = this.db.select().from(siteLivenessState).where(eq24(siteLivenessState.projectId, projectId)).get();
15014
15093
  const sincePrevious = previous ? Date.parse(checkedAt) - Date.parse(previous.checkedAt) : Number.NaN;
15015
15094
  const countsAsRepeat = Number.isFinite(sincePrevious) && sincePrevious >= SITE_LIVENESS_MIN_PASS_GAP_MS && sincePrevious <= SITE_LIVENESS_MAX_PASS_GAP_MS;
15016
15095
  const failures = check.status === "fail" ? countsAsRepeat ? (previous?.consecutiveFailures ?? 0) + 1 : 1 : 0;
@@ -15020,7 +15099,7 @@ var Notifier = class {
15020
15099
  if (check.status === "ok" && outagePaged) event = "health.recovered";
15021
15100
  const confirmed = check.status === "fail" && failures >= SITE_LIVENESS_FAILURES_TO_PAGE;
15022
15101
  const urgentRecord = previous?.urgentNotifiedAt ?? null;
15023
- const enabled = event !== null || confirmed || urgentRecord !== null ? this.db.select().from(notifications).where(eq23(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
15102
+ const enabled = event !== null || confirmed || urgentRecord !== null ? this.db.select().from(notifications).where(eq24(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
15024
15103
  const listedEvents = (notif) => notif.config.events;
15025
15104
  const hasUrl = (notif) => Boolean(notif.config.url);
15026
15105
  const scoped = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
@@ -15053,7 +15132,7 @@ var Notifier = class {
15053
15132
  if (previous === void 0) {
15054
15133
  this.db.insert(siteLivenessState).values(observation).run();
15055
15134
  } else {
15056
- this.db.update(siteLivenessState).set(observation).where(eq23(siteLivenessState.projectId, projectId)).run();
15135
+ this.db.update(siteLivenessState).set(observation).where(eq24(siteLivenessState.projectId, projectId)).run();
15057
15136
  }
15058
15137
  if (!event && urgentGroups.every((group) => group.urgentEvent === null)) return null;
15059
15138
  const payloadFor = (name) => {
@@ -15089,9 +15168,9 @@ var Notifier = class {
15089
15168
  }
15090
15169
  }
15091
15170
  if (event === "health.degraded" && delivered > 0) {
15092
- this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(eq23(siteLivenessState.projectId, projectId)).run();
15171
+ this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(eq24(siteLivenessState.projectId, projectId)).run();
15093
15172
  }
15094
- log22.info("site-liveness.notified", { projectId, event, code: check.code, subscribers: notifs.length, delivered });
15173
+ log23.info("site-liveness.notified", { projectId, event, code: check.code, subscribers: notifs.length, delivered });
15095
15174
  }
15096
15175
  for (const group of urgentGroups) {
15097
15176
  const { urgentEvent, urgentNotifs, generalIds } = group;
@@ -15103,10 +15182,10 @@ var Notifier = class {
15103
15182
  }
15104
15183
  if (accepted > 0) {
15105
15184
  recordUrgentReceipts(urgentReceipts, group.subscribers, group.settled);
15106
- this.db.update(siteLivenessState).set({ urgentNotifiedAt: siteUrgentSummary(urgentReceipts), urgentReceipts }).where(eq23(siteLivenessState.projectId, projectId)).run();
15185
+ this.db.update(siteLivenessState).set({ urgentNotifiedAt: siteUrgentSummary(urgentReceipts), urgentReceipts }).where(eq24(siteLivenessState.projectId, projectId)).run();
15107
15186
  }
15108
15187
  if (urgentNotifs.length > 0 || generalIds.size > 0) {
15109
- log22.info("site-liveness.urgent-notified", { projectId, event: urgentEvent, code: check.code, subscribers: urgentNotifs.length + generalIds.size, delivered: accepted });
15188
+ log23.info("site-liveness.urgent-notified", { projectId, event: urgentEvent, code: check.code, subscribers: urgentNotifs.length + generalIds.size, delivered: accepted });
15110
15189
  }
15111
15190
  }
15112
15191
  return event;
@@ -15129,7 +15208,7 @@ var Notifier = class {
15129
15208
  */
15130
15209
  isNewInsightNews(projectId, insight) {
15131
15210
  const key = insightNotifyKey(projectId, insight);
15132
- const previous = this.db.select().from(insightNotifyState).where(eq23(insightNotifyState.key, key)).get();
15211
+ const previous = this.db.select().from(insightNotifyState).where(eq24(insightNotifyState.key, key)).get();
15133
15212
  if (!previous) return true;
15134
15213
  if (severityRank(insight.severity) > severityRank(previous.severity)) return true;
15135
15214
  const magnitude = insightMagnitude(insight);
@@ -15151,9 +15230,9 @@ var Notifier = class {
15151
15230
  magnitude: insightMagnitude(insight),
15152
15231
  notifiedAt: (/* @__PURE__ */ new Date()).toISOString()
15153
15232
  };
15154
- const existing = this.db.select().from(insightNotifyState).where(eq23(insightNotifyState.key, key)).get();
15233
+ const existing = this.db.select().from(insightNotifyState).where(eq24(insightNotifyState.key, key)).get();
15155
15234
  if (existing) {
15156
- this.db.update(insightNotifyState).set(row).where(eq23(insightNotifyState.key, key)).run();
15235
+ this.db.update(insightNotifyState).set(row).where(eq24(insightNotifyState.key, key)).run();
15157
15236
  } else {
15158
15237
  this.db.insert(insightNotifyState).values(row).run();
15159
15238
  }
@@ -15167,11 +15246,11 @@ var Notifier = class {
15167
15246
  if (criticalInsights.length > 0) insightEvents.push("insight.critical");
15168
15247
  if (highInsights.length > 0) insightEvents.push("insight.high");
15169
15248
  if (insightEvents.length === 0) return;
15170
- const notifs = this.db.select().from(notifications).where(eq23(notifications.projectId, projectId)).all().filter((n) => n.enabled);
15249
+ const notifs = this.db.select().from(notifications).where(eq24(notifications.projectId, projectId)).all().filter((n) => n.enabled);
15171
15250
  if (notifs.length === 0) return;
15172
- const run = this.db.select().from(runs).where(eq23(runs.id, runId)).get();
15251
+ const run = this.db.select().from(runs).where(eq24(runs.id, runId)).get();
15173
15252
  if (!run) return;
15174
- const project = this.db.select().from(projects).where(eq23(projects.id, projectId)).get();
15253
+ const project = this.db.select().from(projects).where(eq24(projects.id, projectId)).get();
15175
15254
  if (!project) return;
15176
15255
  for (const notif of notifs) {
15177
15256
  const config = notif.config;
@@ -15252,9 +15331,9 @@ var Notifier = class {
15252
15331
  return delivered;
15253
15332
  }
15254
15333
  async dispatchQueuedReviewAlerts(projectId) {
15255
- const pendingReviews = this.db.select().from(gbpReviews).where(and20(eq23(gbpReviews.projectId, projectId), eq23(gbpReviews.alertState, "pending"))).all();
15256
- const pendingRatings = this.db.select().from(gbpReviewRatings).where(and20(eq23(gbpReviewRatings.projectId, projectId), eq23(gbpReviewRatings.alertState, "pending"))).all();
15257
- const project = this.db.select().from(projects).where(eq23(projects.id, projectId)).get();
15334
+ const pendingReviews = this.db.select().from(gbpReviews).where(and21(eq24(gbpReviews.projectId, projectId), eq24(gbpReviews.alertState, "pending"))).all();
15335
+ const pendingRatings = this.db.select().from(gbpReviewRatings).where(and21(eq24(gbpReviewRatings.projectId, projectId), eq24(gbpReviewRatings.alertState, "pending"))).all();
15336
+ const project = this.db.select().from(projects).where(eq24(projects.id, projectId)).get();
15258
15337
  if (!project) return { reviews: 0, ratingDrops: 0, delivered: 0 };
15259
15338
  const chatSendsLeft = /* @__PURE__ */ new Map();
15260
15339
  const now = /* @__PURE__ */ new Date();
@@ -15266,10 +15345,10 @@ var Notifier = class {
15266
15345
  displayName: gbpLocations.displayName,
15267
15346
  mapsUri: gbpLocations.mapsUri,
15268
15347
  reviewsAccess: gbpLocations.reviewsAccess
15269
- }).from(gbpLocations).where(eq23(gbpLocations.projectId, projectId)).all().map((loc) => [loc.locationName, loc])
15348
+ }).from(gbpLocations).where(eq24(gbpLocations.projectId, projectId)).all().map((loc) => [loc.locationName, loc])
15270
15349
  );
15271
15350
  const v4HasWorked = new Set(
15272
- this.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and20(eq23(gbpReviewRatings.projectId, projectId), eq23(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
15351
+ this.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and21(eq24(gbpReviewRatings.projectId, projectId), eq24(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
15273
15352
  );
15274
15353
  const coveredByBusinessProfile = (locationName) => businessProfileReviewsCover(locations.get(locationName)?.reviewsAccess ?? null, v4HasWorked.has(locationName));
15275
15354
  const locationRef = (locationName) => {
@@ -15298,7 +15377,7 @@ var Notifier = class {
15298
15377
  else if (row.rating === null || row.previousRating === null) setRatingState([row.id], "none");
15299
15378
  else ratingsToSend.push(row);
15300
15379
  }
15301
- const enabled = this.db.select().from(notifications).where(eq23(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url);
15380
+ const enabled = this.db.select().from(notifications).where(eq24(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url);
15302
15381
  const subscribedTo = (event) => enabled.filter((n) => (n.config.events ?? []).includes(event));
15303
15382
  const projectRef = { name: project.name, canonicalDomain: project.canonicalDomain };
15304
15383
  const dashboardUrl = `${this.serverUrl}/projects/${project.name}`;
@@ -15352,13 +15431,13 @@ var Notifier = class {
15352
15431
  if (!sent) all = false;
15353
15432
  }
15354
15433
  if (all) {
15355
- this.db.update(gbpReviews).set({ alertState: "sent", alertStateAt: nowIso }).where(and20(
15356
- eq23(gbpReviews.id, review.id),
15357
- eq23(gbpReviews.alertState, "pending"),
15358
- eq23(gbpReviews.updateTime, review.updateTime),
15359
- review.alertStateAt === null ? isNull5(gbpReviews.alertStateAt) : eq23(gbpReviews.alertStateAt, review.alertStateAt),
15360
- review.comment === null ? isNull5(gbpReviews.comment) : eq23(gbpReviews.comment, review.comment),
15361
- review.starRating === null ? isNull5(gbpReviews.starRating) : eq23(gbpReviews.starRating, review.starRating)
15434
+ this.db.update(gbpReviews).set({ alertState: "sent", alertStateAt: nowIso }).where(and21(
15435
+ eq24(gbpReviews.id, review.id),
15436
+ eq24(gbpReviews.alertState, "pending"),
15437
+ eq24(gbpReviews.updateTime, review.updateTime),
15438
+ review.alertStateAt === null ? isNull5(gbpReviews.alertStateAt) : eq24(gbpReviews.alertStateAt, review.alertStateAt),
15439
+ review.comment === null ? isNull5(gbpReviews.comment) : eq24(gbpReviews.comment, review.comment),
15440
+ review.starRating === null ? isNull5(gbpReviews.starRating) : eq24(gbpReviews.starRating, review.starRating)
15362
15441
  )).run();
15363
15442
  }
15364
15443
  }
@@ -15390,7 +15469,7 @@ var Notifier = class {
15390
15469
  delivered += accepted;
15391
15470
  }
15392
15471
  }
15393
- log22.info("reviews.dispatched", {
15472
+ log23.info("reviews.dispatched", {
15394
15473
  projectId,
15395
15474
  reviews: reviewsToSend.length,
15396
15475
  ratingDrops: ratingsToSend.length,
@@ -15399,21 +15478,21 @@ var Notifier = class {
15399
15478
  return { reviews: reviewsToSend.length, ratingDrops: ratingsToSend.length, delivered };
15400
15479
  }
15401
15480
  computeTransitions(runId, projectId) {
15402
- const thisRun = this.db.select().from(runs).where(eq23(runs.id, runId)).get();
15481
+ const thisRun = this.db.select().from(runs).where(eq24(runs.id, runId)).get();
15403
15482
  if (!thisRun) return [];
15404
15483
  const completeness = measurementRunCompleteness(this.db, runId);
15405
15484
  if (completeness.planned && !completeness.complete) {
15406
- log22.info("transitions.skipped-incomplete", {
15485
+ log23.info("transitions.skipped-incomplete", {
15407
15486
  runId,
15408
15487
  executed: completeness.executed,
15409
15488
  expected: completeness.expected
15410
15489
  });
15411
15490
  return [];
15412
15491
  }
15413
- const groupSiblings = this.db.select().from(runs).where(and20(
15414
- eq23(runs.projectId, projectId),
15415
- eq23(runs.kind, thisRun.kind),
15416
- eq23(runs.createdAt, thisRun.createdAt),
15492
+ const groupSiblings = this.db.select().from(runs).where(and21(
15493
+ eq24(runs.projectId, projectId),
15494
+ eq24(runs.kind, thisRun.kind),
15495
+ eq24(runs.createdAt, thisRun.createdAt),
15417
15496
  notProbeRun()
15418
15497
  )).all();
15419
15498
  const stillPending = groupSiblings.some((r) => r.status === "queued" || r.status === "running");
@@ -15430,20 +15509,20 @@ var Notifier = class {
15430
15509
  return candidate.id > best.id ? candidate : best;
15431
15510
  });
15432
15511
  if (winner.id !== runId) return [];
15433
- const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq23(projects.id, projectId)).get();
15512
+ const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq24(projects.id, projectId)).get();
15434
15513
  const locationCount = Math.max(
15435
15514
  1,
15436
15515
  (projectLocations?.locations ?? []).length
15437
15516
  );
15438
15517
  const RECENT_FETCH_LIMIT = Math.max(8, locationCount * 4);
15439
15518
  const recentRuns = this.db.select().from(runs).where(
15440
- and20(
15441
- eq23(runs.projectId, projectId),
15442
- eq23(runs.kind, thisRun.kind),
15443
- or2(eq23(runs.status, "completed"), eq23(runs.status, "partial")),
15519
+ and21(
15520
+ eq24(runs.projectId, projectId),
15521
+ eq24(runs.kind, thisRun.kind),
15522
+ or2(eq24(runs.status, "completed"), eq24(runs.status, "partial")),
15444
15523
  notProbeRun()
15445
15524
  )
15446
- ).orderBy(desc8(runs.createdAt), desc8(runs.id)).limit(RECENT_FETCH_LIMIT).all();
15525
+ ).orderBy(desc9(runs.createdAt), desc9(runs.id)).limit(RECENT_FETCH_LIMIT).all();
15447
15526
  const groups = groupRunsByCreatedAt(recentRuns);
15448
15527
  const currentGroupIdx = groups.findIndex((g) => g[0]?.createdAt === thisRun.createdAt);
15449
15528
  if (currentGroupIdx < 0) return [];
@@ -15458,7 +15537,7 @@ var Notifier = class {
15458
15537
  provider: querySnapshots.provider,
15459
15538
  location: querySnapshots.location,
15460
15539
  citationState: querySnapshots.citationState
15461
- }).from(querySnapshots).leftJoin(queries, eq23(querySnapshots.queryId, queries.id)).where(inArray12(querySnapshots.runId, currentRunIds)).all();
15540
+ }).from(querySnapshots).leftJoin(queries, eq24(querySnapshots.queryId, queries.id)).where(inArray12(querySnapshots.runId, currentRunIds)).all();
15462
15541
  const previousSnapshots = this.db.select({
15463
15542
  queryId: querySnapshots.queryId,
15464
15543
  provider: querySnapshots.provider,
@@ -15488,6 +15567,55 @@ var Notifier = class {
15488
15567
  return transitions;
15489
15568
  }
15490
15569
  /** True only when the destination accepted the payload. Callers that record "already said" must not count an attempt. */
15570
+ /**
15571
+ * Send `traffic.agent.discovered` for bots and agents no rule recognizes:
15572
+ * one message per project listing the names first seen in the last 7 days
15573
+ * that reached 3 hits. A name is decided once: `sent` when a webhook accepted
15574
+ * it, `skipped` when nothing subscribed, so subscribing later starts from new
15575
+ * names, `stale` when first seen before the window, and `baseline` when first
15576
+ * seen in the project's first 7 days of recorded traffic. When every delivery
15577
+ * fails the names stay undecided and the next pass retries them until they
15578
+ * age out of the window. Returns how many names were sent.
15579
+ */
15580
+ async dispatchUnrecognizedAgentAlerts(now = Date.now()) {
15581
+ if (this.unrecognizedAgentDispatchInFlight) return 0;
15582
+ this.unrecognizedAgentDispatchInFlight = true;
15583
+ let sentNames = 0;
15584
+ try {
15585
+ for (const projectId of projectsWithRecentUnrecognizedAgents(this.db, now)) {
15586
+ const { due, stale, baseline } = undecidedUnrecognizedAgents(this.db, projectId, now);
15587
+ recordUnrecognizedAgentDecisions(this.db, projectId, stale, UnrecognizedAgentNotifyStates.stale, now);
15588
+ recordUnrecognizedAgentDecisions(this.db, projectId, baseline, UnrecognizedAgentNotifyStates.baseline, now);
15589
+ if (due.length === 0) continue;
15590
+ const names = due.map((agent) => agent.name);
15591
+ const subscribed = this.db.select().from(notifications).where(eq24(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url && n.config.events.includes("traffic.agent.discovered"));
15592
+ if (subscribed.length === 0) {
15593
+ recordUnrecognizedAgentDecisions(this.db, projectId, names, UnrecognizedAgentNotifyStates.skipped, now);
15594
+ continue;
15595
+ }
15596
+ const project = this.db.select().from(projects).where(eq24(projects.id, projectId)).get();
15597
+ if (!project) continue;
15598
+ const payload = {
15599
+ source: "canonry",
15600
+ event: "traffic.agent.discovered",
15601
+ project: { name: project.name, canonicalDomain: project.canonicalDomain },
15602
+ agents: due,
15603
+ dashboardUrl: `${this.serverUrl}/projects/${project.name}`
15604
+ };
15605
+ let accepted = false;
15606
+ for (const notif of subscribed) {
15607
+ if (await this.sendWebhook(notif.config.url, payload, notif.id, projectId, notif.webhookSecret ?? null)) accepted = true;
15608
+ }
15609
+ if (accepted) {
15610
+ recordUnrecognizedAgentDecisions(this.db, projectId, names, UnrecognizedAgentNotifyStates.sent, now);
15611
+ sentNames += names.length;
15612
+ }
15613
+ }
15614
+ } finally {
15615
+ this.unrecognizedAgentDispatchInFlight = false;
15616
+ }
15617
+ return sentNames;
15618
+ }
15491
15619
  async sendWebhook(url, payload, notificationId, projectId, webhookSecret) {
15492
15620
  const elapsed = startOutcomeTimer();
15493
15621
  const destination = resolveDestination(url);
@@ -15496,7 +15624,7 @@ var Notifier = class {
15496
15624
  const targetLabel = redactNotificationUrl(url).urlDisplay;
15497
15625
  const targetCheck = await resolveWebhookTarget(url, { allowLoopback: this.allowLoopbackWebhooks });
15498
15626
  if (!targetCheck.ok) {
15499
- log22.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
15627
+ log23.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
15500
15628
  this.logDelivery(projectId, notificationId, payload.event, "failed", `SSRF: ${targetCheck.message}`);
15501
15629
  const reasonCode = webhookTargetRefusalReason(targetCheck);
15502
15630
  trackWebhookDelivery({
@@ -15509,7 +15637,7 @@ var Notifier = class {
15509
15637
  });
15510
15638
  return false;
15511
15639
  }
15512
- log22.info("webhook.send", { event: payload.event, url: targetLabel });
15640
+ log23.info("webhook.send", { event: payload.event, url: targetLabel });
15513
15641
  const maxRetries = 3;
15514
15642
  const delays = [1e3, 4e3, 16e3];
15515
15643
  let lastStatusClass = "network";
@@ -15518,13 +15646,13 @@ var Notifier = class {
15518
15646
  try {
15519
15647
  const response = await deliverWebhook(targetCheck.target, body, signingSecret);
15520
15648
  if (response.status >= 200 && response.status < 300) {
15521
- log22.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
15649
+ log23.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
15522
15650
  this.logDelivery(projectId, notificationId, payload.event, "sent", null);
15523
15651
  trackWebhookDelivery({ url, event: payload.event, attempts: attempt + 1, durationBucket: elapsed(), statusClass: "2xx" });
15524
15652
  return true;
15525
15653
  }
15526
15654
  const errorDetail = response.error ?? `HTTP ${response.status}`;
15527
- log22.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
15655
+ log23.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
15528
15656
  lastStatusClass = response.status === 0 && response.timedOut ? "timeout" : statusClassOf(response.status);
15529
15657
  lastFailure = { reasonCode: webhookResponseReason(response) ?? "UNKNOWN" };
15530
15658
  if (attempt === maxRetries - 1) {
@@ -15536,7 +15664,7 @@ var Notifier = class {
15536
15664
  lastFailure = outcomeFailure(err);
15537
15665
  if (attempt === maxRetries - 1) {
15538
15666
  this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
15539
- log22.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
15667
+ log23.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
15540
15668
  }
15541
15669
  }
15542
15670
  if (attempt < maxRetries - 1) {
@@ -15568,8 +15696,8 @@ var Notifier = class {
15568
15696
  };
15569
15697
 
15570
15698
  // src/run-coordinator.ts
15571
- import { eq as eq24 } from "drizzle-orm";
15572
- var log23 = createLogger("RunCoordinator");
15699
+ import { eq as eq25 } from "drizzle-orm";
15700
+ var log24 = createLogger("RunCoordinator");
15573
15701
  function reportInsightsGenerated(runTrigger, outcome) {
15574
15702
  trackFeatureCompleted({
15575
15703
  feature: "insights",
@@ -15602,22 +15730,22 @@ var RunCoordinator = class {
15602
15730
  */
15603
15731
  onAnswersRecorded;
15604
15732
  async onRunCompleted(runId, projectId, opts) {
15605
- const runRow = this.db.select().from(runs).where(eq24(runs.id, runId)).get();
15733
+ const runRow = this.db.select().from(runs).where(eq25(runs.id, runId)).get();
15606
15734
  const kind = runRow?.kind ?? RunKinds["answer-visibility"];
15607
15735
  if (runRow?.trigger === RunTriggers.probe) {
15608
- log23.info("probe.skip-side-effects", { runId, projectId, kind });
15736
+ log24.info("probe.skip-side-effects", { runId, projectId, kind });
15609
15737
  return;
15610
15738
  }
15611
15739
  if (kind === RunKinds["answer-visibility"] && (runRow?.status === RunStatuses.completed || runRow?.status === RunStatuses.partial) && this.onAnswersRecorded) {
15612
15740
  try {
15613
15741
  await this.onAnswersRecorded(runId, projectId);
15614
15742
  } catch (err) {
15615
- log23.error("answers-recorded.failed", { runId, projectId, error: describeError(err) });
15743
+ log24.error("answers-recorded.failed", { runId, projectId, error: describeError(err) });
15616
15744
  }
15617
15745
  }
15618
15746
  const completeness = measurementRunCompleteness(this.db, runId);
15619
15747
  if (completeness.planned && !completeness.complete) {
15620
- log23.info("measurement.incomplete-skip-derived", {
15748
+ log24.info("measurement.incomplete-skip-derived", {
15621
15749
  runId,
15622
15750
  projectId,
15623
15751
  executed: completeness.executed,
@@ -15626,7 +15754,7 @@ var RunCoordinator = class {
15626
15754
  try {
15627
15755
  await this.notifier.onRunCompleted(runId, projectId);
15628
15756
  } catch (err) {
15629
- log23.error("notifier.failed", { runId, error: describeError(err) });
15757
+ log24.error("notifier.failed", { runId, error: describeError(err) });
15630
15758
  }
15631
15759
  return;
15632
15760
  }
@@ -15634,7 +15762,7 @@ var RunCoordinator = class {
15634
15762
  try {
15635
15763
  await this.onSentimentCompleted(runId, projectId);
15636
15764
  } catch {
15637
- log23.error("sentiment.wakeup-failed", { runId, projectId });
15765
+ log24.error("sentiment.wakeup-failed", { runId, projectId });
15638
15766
  }
15639
15767
  }
15640
15768
  let insightCount = 0;
@@ -15654,12 +15782,12 @@ var RunCoordinator = class {
15654
15782
  try {
15655
15783
  await this.onInsightsGenerated(runId, projectId, result);
15656
15784
  } catch (err) {
15657
- log23.error("insight-webhook.failed", { runId, error: describeError(err) });
15785
+ log24.error("insight-webhook.failed", { runId, error: describeError(err) });
15658
15786
  }
15659
15787
  }
15660
15788
  }
15661
15789
  } catch (err) {
15662
- log23.error("intelligence.failed", { runId, error: describeError(err) });
15790
+ log24.error("intelligence.failed", { runId, error: describeError(err) });
15663
15791
  insightsOutcome = { status: OutcomeStatuses.failed, ...outcomeFailure(err), durationBucket: elapsed() };
15664
15792
  }
15665
15793
  reportInsightsGenerated(runRow?.trigger, insightsOutcome);
@@ -15677,24 +15805,24 @@ var RunCoordinator = class {
15677
15805
  try {
15678
15806
  await this.onInsightsGenerated(runId, projectId, analysisResultFromInsights(gbpInsights));
15679
15807
  } catch (err) {
15680
- log23.error("gbp-insight-webhook.failed", { runId, error: describeError(err) });
15808
+ log24.error("gbp-insight-webhook.failed", { runId, error: describeError(err) });
15681
15809
  }
15682
15810
  }
15683
15811
  } catch (err) {
15684
- log23.error("gbp-intelligence.failed", { runId, error: describeError(err) });
15812
+ log24.error("gbp-intelligence.failed", { runId, error: describeError(err) });
15685
15813
  insightsOutcome = { status: OutcomeStatuses.failed, ...outcomeFailure(err), durationBucket: elapsed() };
15686
15814
  }
15687
15815
  reportInsightsGenerated(runRow?.trigger, insightsOutcome);
15688
15816
  try {
15689
15817
  await this.notifier.dispatchReviewAlerts(projectId);
15690
15818
  } catch (err) {
15691
- log23.error("review-webhook.failed", { runId, error: describeError(err) });
15819
+ log24.error("review-webhook.failed", { runId, error: describeError(err) });
15692
15820
  }
15693
15821
  }
15694
15822
  try {
15695
15823
  await (opts ? this.notifier.onRunCompleted(runId, projectId, opts) : this.notifier.onRunCompleted(runId, projectId));
15696
15824
  } catch (err) {
15697
- log23.error("notifier.failed", { runId, error: describeError(err) });
15825
+ log24.error("notifier.failed", { runId, error: describeError(err) });
15698
15826
  }
15699
15827
  if (this.onAeroEvent) {
15700
15828
  try {
@@ -15707,7 +15835,7 @@ var RunCoordinator = class {
15707
15835
  };
15708
15836
  await this.onAeroEvent(ctx);
15709
15837
  } catch (err) {
15710
- log23.error("aero.failed", { runId, error: describeError(err) });
15838
+ log24.error("aero.failed", { runId, error: describeError(err) });
15711
15839
  }
15712
15840
  }
15713
15841
  }
@@ -15722,7 +15850,7 @@ var RunCoordinator = class {
15722
15850
  * so the Aero queue is never starved of a follow-up.
15723
15851
  */
15724
15852
  buildDiscoveryAeroContext(runId, projectId, status, error) {
15725
- const session = this.db.select().from(discoverySessions).where(eq24(discoverySessions.runId, runId)).get();
15853
+ const session = this.db.select().from(discoverySessions).where(eq25(discoverySessions.runId, runId)).get();
15726
15854
  const competitorMap = session ? session.competitorMap : [];
15727
15855
  return {
15728
15856
  kind: RunKinds["aeo-discover-probe"],
@@ -15765,12 +15893,12 @@ function analysisResultFromInsights(insights2) {
15765
15893
 
15766
15894
  // src/agent/session-registry.ts
15767
15895
  import crypto23 from "crypto";
15768
- import { eq as eq27 } from "drizzle-orm";
15896
+ import { eq as eq28 } from "drizzle-orm";
15769
15897
 
15770
15898
  // src/agent/remote-mcp.ts
15771
15899
  import { Type } from "@earendil-works/pi-ai";
15772
15900
  import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
15773
- var log24 = createLogger("RemoteMcp");
15901
+ var log25 = createLogger("RemoteMcp");
15774
15902
  var AERO_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([]);
15775
15903
  async function connectStreamableHttp(server) {
15776
15904
  const transport = new StreamableHTTPClientTransport(new URL(server.url), {
@@ -15838,7 +15966,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
15838
15966
  try {
15839
15967
  client = await connect(server);
15840
15968
  } catch (err) {
15841
- log24.error("external-mcp.connect-failed", {
15969
+ log25.error("external-mcp.connect-failed", {
15842
15970
  label,
15843
15971
  error: describeError(err)
15844
15972
  });
@@ -15848,7 +15976,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
15848
15976
  try {
15849
15977
  listed = await client.listTools();
15850
15978
  } catch (err) {
15851
- log24.error("external-mcp.list-failed", {
15979
+ log25.error("external-mcp.list-failed", {
15852
15980
  label,
15853
15981
  error: describeError(err)
15854
15982
  });
@@ -15859,7 +15987,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
15859
15987
  if (!isReadOnly(tool)) continue;
15860
15988
  tools.push(adaptRemoteTool(client, tool));
15861
15989
  }
15862
- log24.info("external-mcp.loaded", {
15990
+ log25.info("external-mcp.loaded", {
15863
15991
  label,
15864
15992
  discovered: listed.tools.length,
15865
15993
  adopted: tools.length
@@ -15870,7 +15998,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
15870
15998
 
15871
15999
  // src/agent/memory-store.ts
15872
16000
  import crypto22 from "crypto";
15873
- import { and as and21, desc as desc9, eq as eq25, like, sql as sql10, or as or3 } from "drizzle-orm";
16001
+ import { and as and22, desc as desc10, eq as eq26, like, sql as sql11, or as or3 } from "drizzle-orm";
15874
16002
  var COMPACTION_KEY_PREFIX = "compaction:";
15875
16003
  var COMPACTION_NOTES_PER_SESSION = 3;
15876
16004
  function rowToDto(row) {
@@ -15884,7 +16012,7 @@ function rowToDto(row) {
15884
16012
  };
15885
16013
  }
15886
16014
  function listMemoryEntries(db, projectId, opts = {}) {
15887
- const query = db.select().from(agentMemory).where(eq25(agentMemory.projectId, projectId)).orderBy(desc9(agentMemory.updatedAt));
16015
+ const query = db.select().from(agentMemory).where(eq26(agentMemory.projectId, projectId)).orderBy(desc10(agentMemory.updatedAt));
15888
16016
  const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
15889
16017
  return rows.map(rowToDto);
15890
16018
  }
@@ -15915,22 +16043,22 @@ function upsertMemoryEntry(db, args) {
15915
16043
  updatedAt: now
15916
16044
  }
15917
16045
  }).run();
15918
- const row = db.select().from(agentMemory).where(and21(eq25(agentMemory.projectId, args.projectId), eq25(agentMemory.key, args.key))).get();
16046
+ const row = db.select().from(agentMemory).where(and22(eq26(agentMemory.projectId, args.projectId), eq26(agentMemory.key, args.key))).get();
15919
16047
  if (!row) throw new Error("memory upsert produced no row");
15920
16048
  return rowToDto(row);
15921
16049
  }
15922
16050
  function deleteMemoryEntry(db, projectId, key) {
15923
- const result = db.delete(agentMemory).where(and21(eq25(agentMemory.projectId, projectId), eq25(agentMemory.key, key))).run();
16051
+ const result = db.delete(agentMemory).where(and22(eq26(agentMemory.projectId, projectId), eq26(agentMemory.key, key))).run();
15924
16052
  const changes = result.changes ?? 0;
15925
16053
  return changes > 0;
15926
16054
  }
15927
16055
  function loadRecentForHydrate(db, projectId, limit, sessionId) {
15928
16056
  if (sessionId === void 0) return listMemoryEntries(db, projectId, { limit });
15929
16057
  const sessionPrefix = `compaction:${sessionId ?? ""}:`;
15930
- return db.select().from(agentMemory).where(and21(
15931
- eq25(agentMemory.projectId, projectId),
15932
- or3(sql10`substr(${agentMemory.key}, 1, ${COMPACTION_KEY_PREFIX.length}) <> ${COMPACTION_KEY_PREFIX}`, sessionId ? sql10`substr(${agentMemory.key}, 1, ${sessionPrefix.length}) = ${sessionPrefix}` : void 0)
15933
- )).orderBy(desc9(agentMemory.updatedAt)).limit(limit).all().map(rowToDto);
16058
+ return db.select().from(agentMemory).where(and22(
16059
+ eq26(agentMemory.projectId, projectId),
16060
+ or3(sql11`substr(${agentMemory.key}, 1, ${COMPACTION_KEY_PREFIX.length}) <> ${COMPACTION_KEY_PREFIX}`, sessionId ? sql11`substr(${agentMemory.key}, 1, ${sessionPrefix.length}) = ${sessionPrefix}` : void 0)
16061
+ )).orderBy(desc10(agentMemory.updatedAt)).limit(limit).all().map(rowToDto);
15934
16062
  }
15935
16063
  function writeCompactionNote(db, args) {
15936
16064
  if (Buffer.byteLength(args.summary, "utf8") > AGENT_MEMORY_VALUE_MAX_BYTES) {
@@ -15954,16 +16082,16 @@ function writeCompactionNote(db, args) {
15954
16082
  }).run();
15955
16083
  const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
15956
16084
  const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
15957
- and21(
15958
- eq25(agentMemory.projectId, args.projectId),
16085
+ and22(
16086
+ eq26(agentMemory.projectId, args.projectId),
15959
16087
  like(agentMemory.key, `${sessionPrefix}%`)
15960
16088
  )
15961
- ).orderBy(desc9(agentMemory.updatedAt)).all();
16089
+ ).orderBy(desc10(agentMemory.updatedAt)).all();
15962
16090
  const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
15963
16091
  if (stale.length > 0) {
15964
- tx.delete(agentMemory).where(sql10`${agentMemory.id} IN (${sql10.join(stale.map((s) => sql10`${s}`), sql10`, `)})`).run();
16092
+ tx.delete(agentMemory).where(sql11`${agentMemory.id} IN (${sql11.join(stale.map((s) => sql11`${s}`), sql11`, `)})`).run();
15965
16093
  }
15966
- const row = tx.select().from(agentMemory).where(and21(eq25(agentMemory.projectId, args.projectId), eq25(agentMemory.key, key))).get();
16094
+ const row = tx.select().from(agentMemory).where(and22(eq26(agentMemory.projectId, args.projectId), eq26(agentMemory.key, key))).get();
15967
16095
  if (row) inserted = rowToDto(row);
15968
16096
  });
15969
16097
  if (!inserted) throw new Error("compaction note write produced no row");
@@ -16037,7 +16165,7 @@ function trackAeroTurn(agent, turn) {
16037
16165
  }
16038
16166
 
16039
16167
  // src/agent/project-shape.ts
16040
- import { and as and22, count as count4, eq as eq26 } from "drizzle-orm";
16168
+ import { and as and23, count as count4, eq as eq27 } from "drizzle-orm";
16041
16169
  var ADVANCED_TOOLS = [
16042
16170
  "canonry_measurement_portfolio_summary",
16043
16171
  "canonry_measurement_overview",
@@ -16064,10 +16192,10 @@ var ADVANCED_ROUTES = [
16064
16192
  ];
16065
16193
  function aeroProjectShape(db, projectId) {
16066
16194
  try {
16067
- const pointer = db.select().from(measurementPlans).where(eq26(measurementPlans.projectId, projectId)).get();
16068
- const version = pointer ? db.select().from(measurementPlanVersions).where(and22(
16069
- eq26(measurementPlanVersions.projectId, projectId),
16070
- eq26(measurementPlanVersions.id, pointer.activeVersionId)
16195
+ const pointer = db.select().from(measurementPlans).where(eq27(measurementPlans.projectId, projectId)).get();
16196
+ const version = pointer ? db.select().from(measurementPlanVersions).where(and23(
16197
+ eq27(measurementPlanVersions.projectId, projectId),
16198
+ eq27(measurementPlanVersions.id, pointer.activeVersionId)
16071
16199
  )).get() : void 0;
16072
16200
  if (version) {
16073
16201
  const plan = JSON.parse(version.canonicalJson);
@@ -16094,7 +16222,7 @@ canonry_visibility_report describes the whole project, not one Property. canonry
16094
16222
  pinned: ADVANCED_TOOLS
16095
16223
  };
16096
16224
  }
16097
- const tracked = db.select({ total: count4() }).from(queries).where(eq26(queries.projectId, projectId)).get()?.total ?? 0;
16225
+ const tracked = db.select({ total: count4() }).from(queries).where(eq27(queries.projectId, projectId)).get()?.total ?? 0;
16098
16226
  return {
16099
16227
  prompt: `
16100
16228
 
@@ -16234,7 +16362,7 @@ async function compactMessages(args) {
16234
16362
  }
16235
16363
 
16236
16364
  // src/agent/session-registry.ts
16237
- var log25 = createLogger("SessionRegistry");
16365
+ var log26 = createLogger("SessionRegistry");
16238
16366
  var MAX_HYDRATE_NOTES = 20;
16239
16367
  var MAX_HYDRATE_BYTES = 32 * 1024;
16240
16368
  function escapeMemoryFragment(value) {
@@ -16293,7 +16421,7 @@ var SessionRegistry = class {
16293
16421
  if (!this.externalToolsPromise) {
16294
16422
  this.externalToolsPromise = loadExternalMcpTools(this.opts.config.externalMcpServers).catch(
16295
16423
  (err) => {
16296
- log25.error("external-mcp.load-failed", {
16424
+ log26.error("external-mcp.load-failed", {
16297
16425
  error: describeError(err)
16298
16426
  });
16299
16427
  return [];
@@ -16350,7 +16478,7 @@ var SessionRegistry = class {
16350
16478
  modelProvider: effectiveProvider,
16351
16479
  modelId: effectiveModelId,
16352
16480
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
16353
- }).where(eq27(agentSessions.projectId, projectId)).run();
16481
+ }).where(eq28(agentSessions.projectId, projectId)).run();
16354
16482
  }
16355
16483
  const agent2 = createAeroSession({
16356
16484
  projectName,
@@ -16569,13 +16697,13 @@ ${lines.join("\n")}
16569
16697
  agent.state.messages = result.messages;
16570
16698
  setAeroSystemPrompt(agent, this.buildHydratedSystemPrompt(projectId, row.systemPrompt));
16571
16699
  this.save(projectName);
16572
- log25.info("compaction.completed", {
16700
+ log26.info("compaction.completed", {
16573
16701
  projectName,
16574
16702
  removedCount: result.removedCount,
16575
16703
  summaryBytes: Buffer.byteLength(result.summary, "utf8")
16576
16704
  });
16577
16705
  } catch (err) {
16578
- log25.error("compaction.failed", {
16706
+ log26.error("compaction.failed", {
16579
16707
  projectName,
16580
16708
  error: describeError(err)
16581
16709
  });
@@ -16669,7 +16797,7 @@ ${lines.join("\n")}
16669
16797
  modelProvider: nextProvider,
16670
16798
  modelId: nextModelId,
16671
16799
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
16672
- }).where(eq27(agentSessions.projectId, projectId)).run();
16800
+ }).where(eq28(agentSessions.projectId, projectId)).run();
16673
16801
  }
16674
16802
  /** Snapshot pending work without consuming it before an atomic conversation switch. */
16675
16803
  prepareConversationChange(projectName) {
@@ -16753,7 +16881,7 @@ ${lines.join("\n")}
16753
16881
  }
16754
16882
  trackAeroTurn(agent, { trigger: OutcomeTriggers.agent });
16755
16883
  } catch (err) {
16756
- log25.error("drain.failed", {
16884
+ log26.error("drain.failed", {
16757
16885
  projectName,
16758
16886
  error: describeError(err)
16759
16887
  });
@@ -16850,11 +16978,11 @@ ${lines.join("\n")}
16850
16978
  return id;
16851
16979
  }
16852
16980
  tryResolveProjectId(projectName) {
16853
- const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq27(projects.name, projectName)).get();
16981
+ const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq28(projects.name, projectName)).get();
16854
16982
  return row?.id;
16855
16983
  }
16856
16984
  loadRow(projectId) {
16857
- const row = this.opts.db.select().from(agentSessions).where(eq27(agentSessions.projectId, projectId)).get();
16985
+ const row = this.opts.db.select().from(agentSessions).where(eq28(agentSessions.projectId, projectId)).get();
16858
16986
  return row ?? null;
16859
16987
  }
16860
16988
  insertRow(params) {
@@ -16874,7 +17002,7 @@ ${lines.join("\n")}
16874
17002
  }
16875
17003
  updateRow(projectId, patch) {
16876
17004
  const now = (/* @__PURE__ */ new Date()).toISOString();
16877
- this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq27(agentSessions.projectId, projectId)).run();
17005
+ this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq28(agentSessions.projectId, projectId)).run();
16878
17006
  }
16879
17007
  /**
16880
17008
  * Store a refreshed prompt snapshot without touching `updatedAt`, which the
@@ -16883,13 +17011,13 @@ ${lines.join("\n")}
16883
17011
  * recently used right after an upgrade.
16884
17012
  */
16885
17013
  persistPromptSnapshot(projectId, systemPrompt) {
16886
- this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq27(agentSessions.projectId, projectId)).run();
17014
+ this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq28(agentSessions.projectId, projectId)).run();
16887
17015
  }
16888
17016
  };
16889
17017
 
16890
17018
  // src/agent/viewer-sessions.ts
16891
17019
  import crypto24 from "crypto";
16892
- import { and as and23, eq as eq28, isNotNull as isNotNull4, like as like2 } from "drizzle-orm";
17020
+ import { and as and24, eq as eq29, isNotNull as isNotNull4, like as like2 } from "drizzle-orm";
16893
17021
  var AERO_VIEWER_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
16894
17022
  CanonryMcpToolNames.canonry_ads_account,
16895
17023
  CanonryMcpToolNames.canonry_ads_geo_search,
@@ -17066,12 +17194,12 @@ var ViewerAeroSessions = class {
17066
17194
  */
17067
17195
  removeKey(id) {
17068
17196
  try {
17069
- this.opts.db.delete(apiKeys).where(eq28(apiKeys.id, id)).run();
17197
+ this.opts.db.delete(apiKeys).where(eq29(apiKeys.id, id)).run();
17070
17198
  } catch {
17071
17199
  try {
17072
17200
  this.opts.db.transaction((tx) => {
17073
- tx.delete(dashboardSessions).where(eq28(dashboardSessions.apiKeyId, id)).run();
17074
- tx.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(eq28(apiKeys.id, id)).run();
17201
+ tx.delete(dashboardSessions).where(eq29(dashboardSessions.apiKeyId, id)).run();
17202
+ tx.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(eq29(apiKeys.id, id)).run();
17075
17203
  });
17076
17204
  } catch {
17077
17205
  }
@@ -17084,7 +17212,7 @@ var ViewerAeroSessions = class {
17084
17212
  */
17085
17213
  revokeOrphanedKeys() {
17086
17214
  try {
17087
- this.opts.db.delete(apiKeys).where(and23(like2(apiKeys.name, `${VIEWER_AERO_KEY_PREFIX}%`), isNotNull4(apiKeys.delegatedUserId))).run();
17215
+ this.opts.db.delete(apiKeys).where(and24(like2(apiKeys.name, `${VIEWER_AERO_KEY_PREFIX}%`), isNotNull4(apiKeys.delegatedUserId))).run();
17088
17216
  } catch {
17089
17217
  }
17090
17218
  }
@@ -17101,7 +17229,7 @@ var ViewerAeroSessions = class {
17101
17229
  // src/mcp-http.ts
17102
17230
  import crypto25 from "crypto";
17103
17231
  import { isIP } from "net";
17104
- import { and as and24, eq as eq29, isNull as isNull6 } from "drizzle-orm";
17232
+ import { and as and25, eq as eq30, isNull as isNull6 } from "drizzle-orm";
17105
17233
  import { NodeStreamableHTTPServerTransport, toNodeHandler, toWebRequest } from "@modelcontextprotocol/node";
17106
17234
  import { McpServer, createMcpHandler, isInitializeRequest, isLegacyRequest } from "@modelcontextprotocol/server";
17107
17235
  var SESSION_IDLE_MS = 10 * 60 * 1e3;
@@ -17207,12 +17335,12 @@ function registerMcpHttpRoutes(scope, opts) {
17207
17335
  try {
17208
17336
  withoutLockWait(() => {
17209
17337
  opts.db.transaction((tx) => {
17210
- tx.delete(dashboardSessions).where(eq29(dashboardSessions.apiKeyId, id)).run();
17211
- tx.update(apiKeys).set({ revokedAt: new Date(now()).toISOString() }).where(and24(eq29(apiKeys.id, id), isNull6(apiKeys.revokedAt))).run();
17338
+ tx.delete(dashboardSessions).where(eq30(dashboardSessions.apiKeyId, id)).run();
17339
+ tx.update(apiKeys).set({ revokedAt: new Date(now()).toISOString() }).where(and25(eq30(apiKeys.id, id), isNull6(apiKeys.revokedAt))).run();
17212
17340
  });
17213
17341
  if (remove) {
17214
17342
  try {
17215
- opts.db.delete(apiKeys).where(eq29(apiKeys.id, id)).run();
17343
+ opts.db.delete(apiKeys).where(eq30(apiKeys.id, id)).run();
17216
17344
  } catch (error) {
17217
17345
  scope.log.warn({ err: error, keyId: id }, "Retained revoked MCP request key after deletion failed");
17218
17346
  }
@@ -17263,14 +17391,14 @@ function registerMcpHttpRoutes(scope, opts) {
17263
17391
  withoutLockWait(() => {
17264
17392
  const cutoff = now() - SESSION_IDLE_MS;
17265
17393
  for (const session of legacyServers) {
17266
- const key = session.sessionKeyId ? opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq29(apiKeys.id, session.sessionKeyId)).get() : void 0;
17394
+ const key = session.sessionKeyId ? opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq30(apiKeys.id, session.sessionKeyId)).get() : void 0;
17267
17395
  if (session.lastSeenAt < cutoff || session.expiresAt !== void 0 && session.expiresAt <= now() || session.sessionKeyId && (!key || key.revokedAt)) {
17268
17396
  void session.close().catch((error) => scope.log.error({ err: error }, "Failed to close expired MCP session"));
17269
17397
  }
17270
17398
  }
17271
17399
  for (const exchange of modernExchanges) {
17272
17400
  if (!exchange.sessionKey) continue;
17273
- const key = opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq29(apiKeys.id, exchange.sessionKey.id)).get();
17401
+ const key = opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq30(apiKeys.id, exchange.sessionKey.id)).get();
17274
17402
  if (!key || key.revokedAt || exchange.sessionKey.expiresAt <= now()) {
17275
17403
  void exchange.close().catch((error) => scope.log.error({ err: error }, "Failed to close expired MCP request"));
17276
17404
  }
@@ -17520,7 +17648,7 @@ function registerMcpHttpRoutes(scope, opts) {
17520
17648
  }
17521
17649
 
17522
17650
  // src/agent/agent-routes.ts
17523
- import { eq as eq30 } from "drizzle-orm";
17651
+ import { eq as eq31 } from "drizzle-orm";
17524
17652
  function viewerAeroCaller(request, opts) {
17525
17653
  if (!opts.viewerSessions) return null;
17526
17654
  const principal = request.principal;
@@ -17560,7 +17688,7 @@ function turnAttribution(request) {
17560
17688
  };
17561
17689
  }
17562
17690
  function resolveProject(db, name) {
17563
- const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq30(projects.name, name)).get();
17691
+ const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq31(projects.name, name)).get();
17564
17692
  if (!row) throw notFound("project", name);
17565
17693
  return row;
17566
17694
  }
@@ -17679,7 +17807,7 @@ function registerAgentRoutes(app, opts) {
17679
17807
  }
17680
17808
  requireInstanceAdministrator(request);
17681
17809
  const project = resolveProject(opts.db, request.params.name);
17682
- const row = opts.db.select().from(agentSessions).where(eq30(agentSessions.projectId, project.id)).get();
17810
+ const row = opts.db.select().from(agentSessions).where(eq31(agentSessions.projectId, project.id)).get();
17683
17811
  const nextTurn = opts.sessionRegistry.nextTurnModel(project.name);
17684
17812
  if (!row) {
17685
17813
  return { conversationId: null, messages: [], modelProvider: null, modelId: null, nextTurn, updatedAt: null };
@@ -17721,7 +17849,7 @@ function registerAgentRoutes(app, opts) {
17721
17849
  const project = resolveProject(opts.db, request.params.name);
17722
17850
  if (opts.sessionRegistry.isBusy(project.name)) throw agentBusy(project.name);
17723
17851
  opts.sessionRegistry.reset(project.name);
17724
- opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq30(agentSessions.projectId, project.id)).run();
17852
+ opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq31(agentSessions.projectId, project.id)).run();
17725
17853
  return { status: "reset" };
17726
17854
  }
17727
17855
  );
@@ -17745,7 +17873,7 @@ function registerAgentRoutes(app, opts) {
17745
17873
  return streamViewerTurn(opts, project, viewerId, body, reply, turnAttribution(request));
17746
17874
  }
17747
17875
  if (body.conversationId !== void 0) {
17748
- const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(eq30(agentSessions.projectId, project.id)).get();
17876
+ const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(eq31(agentSessions.projectId, project.id)).get();
17749
17877
  if ((current?.id ?? null) !== body.conversationId) throw validationError("The active conversation changed. Reload the conversation before sending.");
17750
17878
  }
17751
17879
  const promptText = body.prompt;
@@ -18156,7 +18284,7 @@ function stripHtml(html) {
18156
18284
  }
18157
18285
 
18158
18286
  // src/snapshot-service.ts
18159
- var log26 = createLogger("Snapshot");
18287
+ var log27 = createLogger("Snapshot");
18160
18288
  var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "muse", "local"];
18161
18289
  var SNAPSHOT_QUERY_COUNT = 6;
18162
18290
  var SnapshotService = class {
@@ -18258,7 +18386,7 @@ var SnapshotService = class {
18258
18386
  return mapAuditReport(report);
18259
18387
  } catch (err) {
18260
18388
  const message = describeError(err);
18261
- log26.warn("audit.failed", { homepageUrl, error: message });
18389
+ log27.warn("audit.failed", { homepageUrl, error: message });
18262
18390
  return {
18263
18391
  url: homepageUrl,
18264
18392
  finalUrl: homepageUrl,
@@ -18287,7 +18415,7 @@ var SnapshotService = class {
18287
18415
  queries: parsedQueries
18288
18416
  };
18289
18417
  } catch (err) {
18290
- log26.warn("profile.generation-failed", {
18418
+ log27.warn("profile.generation-failed", {
18291
18419
  domain: ctx.domain,
18292
18420
  provider: ctx.analysisProvider.adapter.name,
18293
18421
  error: describeError(err)
@@ -18435,7 +18563,7 @@ var SnapshotService = class {
18435
18563
  recommendedActions: uniqueStrings2(parsed.recommendedActions ?? []).slice(0, 4)
18436
18564
  };
18437
18565
  } catch (err) {
18438
- log26.warn("response.analysis-failed", {
18566
+ log27.warn("response.analysis-failed", {
18439
18567
  provider: ctx.analysisProvider.adapter.name,
18440
18568
  error: describeError(err)
18441
18569
  });
@@ -18705,7 +18833,7 @@ function clipText(value, length) {
18705
18833
  }
18706
18834
 
18707
18835
  // src/research-runner.ts
18708
- import { and as and25, eq as eq31, inArray as inArray13, sql as sql11 } from "drizzle-orm";
18836
+ import { and as and26, eq as eq32, inArray as inArray13, sql as sql12 } from "drizzle-orm";
18709
18837
  var unfinishedResearchQueryStatuses = [ResearchQueryStatuses.queued, ResearchQueryStatuses.running];
18710
18838
  function finalResearchRunStatus(completed, failed, waiting) {
18711
18839
  if (waiting > 0) return ResearchRunStatuses["waiting-upstream"];
@@ -18713,13 +18841,13 @@ function finalResearchRunStatus(completed, failed, waiting) {
18713
18841
  return completed > 0 ? ResearchRunStatuses.partial : ResearchRunStatuses.failed;
18714
18842
  }
18715
18843
  async function executeResearchRun(db, registry, runId, projectId, options = {}) {
18716
- const run = db.select().from(researchRuns).where(and25(eq31(researchRuns.id, runId), eq31(researchRuns.projectId, projectId))).get();
18844
+ const run = db.select().from(researchRuns).where(and26(eq32(researchRuns.id, runId), eq32(researchRuns.projectId, projectId))).get();
18717
18845
  if (!run || run.status !== ResearchRunStatuses.queued) return;
18718
- const project = db.select().from(projects).where(eq31(projects.id, projectId)).get();
18846
+ const project = db.select().from(projects).where(eq32(projects.id, projectId)).get();
18719
18847
  if (!project) return;
18720
18848
  const provider = registry.get(run.provider);
18721
18849
  const now = (/* @__PURE__ */ new Date()).toISOString();
18722
- const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(and25(eq31(researchRuns.id, runId), eq31(researchRuns.status, ResearchRunStatuses.queued))).run();
18850
+ const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(and26(eq32(researchRuns.id, runId), eq32(researchRuns.status, ResearchRunStatuses.queued))).run();
18723
18851
  if (claim.changes !== 1) return;
18724
18852
  const elapsed = startOutcomeTimer();
18725
18853
  const requested = currentOutcomeOrigin();
@@ -18753,8 +18881,8 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
18753
18881
  }
18754
18882
  reserved = run.totalQueries;
18755
18883
  reservation = { scope, period };
18756
- const rows = db.select().from(researchRunQueries).where(eq31(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
18757
- const competitorRows = db.select(competitorIdentityColumns).from(competitors).where(eq31(competitors.projectId, projectId)).orderBy(competitors.domain).all();
18884
+ const rows = db.select().from(researchRunQueries).where(eq32(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
18885
+ const competitorRows = db.select(competitorIdentityColumns).from(competitors).where(eq32(competitors.projectId, projectId)).orderBy(competitors.domain).all();
18758
18886
  const competitorDomains = competitorRows.map((row) => row.domain);
18759
18887
  const competitorAliases = new Map(competitorRows.map((row) => [row.domain, competitorIdentityAliases(row)]).filter(([, aliases]) => aliases.length > 0));
18760
18888
  const domains = effectiveDomains(project);
@@ -18764,7 +18892,7 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
18764
18892
  await mapWithConcurrency(rows, Math.max(1, provider.config.quotaPolicy.maxConcurrency), async (row) => {
18765
18893
  try {
18766
18894
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
18767
- db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(and25(eq31(researchRunQueries.id, row.id), eq31(researchRunQueries.status, ResearchQueryStatuses.queued))).run();
18895
+ db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(and26(eq32(researchRunQueries.id, row.id), eq32(researchRunQueries.status, ResearchQueryStatuses.queued))).run();
18768
18896
  const raw = await gate.run(async () => {
18769
18897
  dispatched++;
18770
18898
  return provider.adapter.executeTrackedQuery({ query: row.queryText, canonicalDomains: domains, competitorDomains, ...run.location ? { location: run.location } : {} }, config);
@@ -18795,7 +18923,7 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
18795
18923
  rawResponse: raw.rawResponse,
18796
18924
  answerEvidence: raw.evidence ?? null,
18797
18925
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
18798
- }).where(and25(eq31(researchRunQueries.id, row.id), eq31(researchRunQueries.status, ResearchQueryStatuses.running))).run();
18926
+ }).where(and26(eq32(researchRunQueries.id, row.id), eq32(researchRunQueries.status, ResearchQueryStatuses.running))).run();
18799
18927
  if (completed.changes === 1) incrementResearchProgress(db, runId, "completedQueries");
18800
18928
  } catch (error) {
18801
18929
  if (!(error instanceof IncompleteAnswerError && error.resumable)) queryFailure ??= error;
@@ -18846,11 +18974,11 @@ function researchOutcomeStatus(status) {
18846
18974
  }
18847
18975
  }
18848
18976
  function incrementResearchProgress(db, runId, column) {
18849
- db.update(researchRuns).set({ [column]: sql11`${researchRuns[column]} + 1` }).where(eq31(researchRuns.id, runId)).run();
18977
+ db.update(researchRuns).set({ [column]: sql12`${researchRuns[column]} + 1` }).where(eq32(researchRuns.id, runId)).run();
18850
18978
  }
18851
18979
  function markResearchQueryFailed(db, runId, queryId, error) {
18852
18980
  const message = describeError(error);
18853
- const unfinished = and25(eq31(researchRunQueries.id, queryId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses));
18981
+ const unfinished = and26(eq32(researchRunQueries.id, queryId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses));
18854
18982
  if (error instanceof IncompleteAnswerError && error.resumable) {
18855
18983
  db.update(researchRunQueries).set({
18856
18984
  status: ResearchQueryStatuses["waiting-upstream"],
@@ -18866,10 +18994,10 @@ function markResearchQueryFailed(db, runId, queryId, error) {
18866
18994
  if (failed.changes === 1) incrementResearchProgress(db, runId, "failedQueries");
18867
18995
  }
18868
18996
  function markUnfinishedResearchQueriesFailed(db, runId, error) {
18869
- db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and25(eq31(researchRunQueries.researchRunId, runId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
18997
+ db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and26(eq32(researchRunQueries.researchRunId, runId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
18870
18998
  }
18871
18999
  function finalizeResearchRun(db, runId, fatalError) {
18872
- const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq31(researchRunQueries.researchRunId, runId)).all();
19000
+ const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq32(researchRunQueries.researchRunId, runId)).all();
18873
19001
  const completed = rows.filter((row) => row.status === ResearchQueryStatuses.completed).length;
18874
19002
  const failed = rows.filter((row) => row.status === ResearchQueryStatuses.failed).length;
18875
19003
  const waiting = rows.filter((row) => row.status === ResearchQueryStatuses["waiting-upstream"]).length;
@@ -18881,12 +19009,12 @@ function finalizeResearchRun(db, runId, fatalError) {
18881
19009
  error: fatalError ?? (failed === rows.length ? "Every research query failed." : null),
18882
19010
  // A run waiting on upstream jobs has not finished.
18883
19011
  finishedAt: waiting > 0 ? null : (/* @__PURE__ */ new Date()).toISOString()
18884
- }).where(eq31(researchRuns.id, runId)).run();
19012
+ }).where(eq32(researchRuns.id, runId)).run();
18885
19013
  return { status, total: rows.length, completed, failed };
18886
19014
  }
18887
19015
 
18888
19016
  // src/google-marketing-sync.ts
18889
- import { and as and26, eq as eq32, isNull as isNull7, sql as sql12 } from "drizzle-orm";
19017
+ import { and as and27, eq as eq33, isNull as isNull7, sql as sql13 } from "drizzle-orm";
18890
19018
  var RUNTIME_FAILURE_REASONS = {
18891
19019
  INVALID_INPUT: OutcomeReasonCodes.VALIDATION,
18892
19020
  NOT_CONFIGURED: OutcomeReasonCodes.NOT_CONNECTED,
@@ -18923,10 +19051,10 @@ function assertSnapshotContext(metadata, expected) {
18923
19051
  }
18924
19052
  }
18925
19053
  function requireSyncRun(db, runId, projectId, kind) {
18926
- const run = db.select().from(runs).where(and26(
18927
- eq32(runs.id, runId),
18928
- eq32(runs.projectId, projectId),
18929
- eq32(runs.kind, kind)
19054
+ const run = db.select().from(runs).where(and27(
19055
+ eq33(runs.id, runId),
19056
+ eq33(runs.projectId, projectId),
19057
+ eq33(runs.kind, kind)
18930
19058
  )).get();
18931
19059
  if (!run) throw withOutcomeReason(new Error(`Run ${runId} is not a ${kind} run for this project.`), OutcomeReasonCodes.NOT_FOUND);
18932
19060
  if (run.status !== RunStatuses.queued) {
@@ -18942,19 +19070,19 @@ function markFailed(db, runId, projectId, kind, expectedStatus, error) {
18942
19070
  status: RunStatuses.failed,
18943
19071
  error: serializeRunError({ message: safeErrorMessage(error) }),
18944
19072
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
18945
- }).where(and26(
18946
- eq32(runs.id, runId),
18947
- eq32(runs.projectId, projectId),
18948
- eq32(runs.kind, kind),
18949
- eq32(runs.status, expectedStatus)
19073
+ }).where(and27(
19074
+ eq33(runs.id, runId),
19075
+ eq33(runs.projectId, projectId),
19076
+ eq33(runs.kind, kind),
19077
+ eq33(runs.status, expectedStatus)
18950
19078
  )).run();
18951
19079
  }
18952
19080
  function claimSyncRun(db, runId, projectId, kind, startedAt) {
18953
- const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(and26(
18954
- eq32(runs.id, runId),
18955
- eq32(runs.projectId, projectId),
18956
- eq32(runs.kind, kind),
18957
- eq32(runs.status, RunStatuses.queued)
19081
+ const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(and27(
19082
+ eq33(runs.id, runId),
19083
+ eq33(runs.projectId, projectId),
19084
+ eq33(runs.kind, kind),
19085
+ eq33(runs.status, RunStatuses.queued)
18958
19086
  )).run();
18959
19087
  return claim.changes === 1;
18960
19088
  }
@@ -18964,9 +19092,9 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
18964
19092
  let evidenceCollected = false;
18965
19093
  try {
18966
19094
  requireSyncRun(db, runId, projectId, RunKinds["google-ads-sync"]);
18967
- const project = db.select().from(projects).where(eq32(projects.id, projectId)).get();
19095
+ const project = db.select().from(projects).where(eq33(projects.id, projectId)).get();
18968
19096
  if (!project) throw withOutcomeReason(new Error(`Project ${projectId} was not found.`), OutcomeReasonCodes.NOT_FOUND);
18969
- const connection = db.select().from(googleAdsConnections).where(eq32(googleAdsConnections.projectId, projectId)).get();
19097
+ const connection = db.select().from(googleAdsConnections).where(eq33(googleAdsConnections.projectId, projectId)).get();
18970
19098
  if (!connection?.selectedCustomerId) {
18971
19099
  throw withOutcomeReason(new Error("Google Ads needs a selected customer before it can sync."), OutcomeReasonCodes.ACCOUNT_NOT_FOUND);
18972
19100
  }
@@ -19018,11 +19146,11 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
19018
19146
  status: RunStatuses.completed,
19019
19147
  finishedAt,
19020
19148
  error: null
19021
- }).where(and26(
19022
- eq32(runs.id, runId),
19023
- eq32(runs.projectId, projectId),
19024
- eq32(runs.kind, RunKinds["google-ads-sync"]),
19025
- eq32(runs.status, RunStatuses.running)
19149
+ }).where(and27(
19150
+ eq33(runs.id, runId),
19151
+ eq33(runs.projectId, projectId),
19152
+ eq33(runs.kind, RunKinds["google-ads-sync"]),
19153
+ eq33(runs.status, RunStatuses.running)
19026
19154
  )).run();
19027
19155
  if (terminalClaim.changes !== 1) {
19028
19156
  throw withOutcomeReason(
@@ -19042,12 +19170,12 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
19042
19170
  lastMetricsSnapshotAt: result.metrics?.metadata.capturedAt ?? connection.lastMetricsSnapshotAt,
19043
19171
  lastMetricsSnapshotId: result.metrics?.metadata.id ?? connection.lastMetricsSnapshotId,
19044
19172
  updatedAt: finishedAt
19045
- }).where(and26(
19046
- eq32(googleAdsConnections.id, connection.id),
19047
- eq32(googleAdsConnections.projectId, projectId),
19048
- eq32(googleAdsConnections.selectedCustomerId, connection.selectedCustomerId),
19049
- connection.selectedLoginCustomerId === null ? isNull7(googleAdsConnections.selectedLoginCustomerId) : eq32(googleAdsConnections.selectedLoginCustomerId, connection.selectedLoginCustomerId),
19050
- eq32(googleAdsConnections.selectionGeneration, connection.selectionGeneration)
19173
+ }).where(and27(
19174
+ eq33(googleAdsConnections.id, connection.id),
19175
+ eq33(googleAdsConnections.projectId, projectId),
19176
+ eq33(googleAdsConnections.selectedCustomerId, connection.selectedCustomerId),
19177
+ connection.selectedLoginCustomerId === null ? isNull7(googleAdsConnections.selectedLoginCustomerId) : eq33(googleAdsConnections.selectedLoginCustomerId, connection.selectedLoginCustomerId),
19178
+ eq33(googleAdsConnections.selectionGeneration, connection.selectionGeneration)
19051
19179
  )).run();
19052
19180
  if (connectionClaim.changes !== 1) {
19053
19181
  throw withOutcomeReason(
@@ -19087,9 +19215,9 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19087
19215
  let evidenceCollected = false;
19088
19216
  try {
19089
19217
  requireSyncRun(db, runId, projectId, RunKinds["gtm-sync"]);
19090
- const project = db.select().from(projects).where(eq32(projects.id, projectId)).get();
19218
+ const project = db.select().from(projects).where(eq33(projects.id, projectId)).get();
19091
19219
  if (!project) throw withOutcomeReason(new Error(`Project ${projectId} was not found.`), OutcomeReasonCodes.NOT_FOUND);
19092
- const connection = db.select().from(gtmConnections).where(eq32(gtmConnections.projectId, projectId)).get();
19220
+ const connection = db.select().from(gtmConnections).where(eq33(gtmConnections.projectId, projectId)).get();
19093
19221
  if (!connection?.selectedAccountId || !connection.selectedContainerId) {
19094
19222
  throw withOutcomeReason(new Error("GTM needs a selected account and container before it can sync."), OutcomeReasonCodes.ACCOUNT_NOT_FOUND);
19095
19223
  }
@@ -19127,11 +19255,11 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19127
19255
  status: RunStatuses.completed,
19128
19256
  finishedAt,
19129
19257
  error: null
19130
- }).where(and26(
19131
- eq32(runs.id, runId),
19132
- eq32(runs.projectId, projectId),
19133
- eq32(runs.kind, RunKinds["gtm-sync"]),
19134
- eq32(runs.status, RunStatuses.running)
19258
+ }).where(and27(
19259
+ eq33(runs.id, runId),
19260
+ eq33(runs.projectId, projectId),
19261
+ eq33(runs.kind, RunKinds["gtm-sync"]),
19262
+ eq33(runs.status, RunStatuses.running)
19135
19263
  )).run();
19136
19264
  if (terminalClaim.changes !== 1) {
19137
19265
  throw withOutcomeReason(
@@ -19146,19 +19274,19 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19146
19274
  selectedWorkspaceName: selectedWorkspace?.name ?? null,
19147
19275
  ...workspaceReplaced ? {
19148
19276
  selectedWorkspaceId: workspaceId,
19149
- selectionGeneration: sql12`${gtmConnections.selectionGeneration} + 1`
19277
+ selectionGeneration: sql13`${gtmConnections.selectionGeneration} + 1`
19150
19278
  } : {},
19151
19279
  lastValidatedAt: snapshot.metadata.capturedAt,
19152
19280
  lastSnapshotAt: snapshot.metadata.capturedAt,
19153
19281
  lastSnapshotId: snapshot.metadata.id,
19154
19282
  updatedAt: finishedAt
19155
- }).where(and26(
19156
- eq32(gtmConnections.id, connection.id),
19157
- eq32(gtmConnections.projectId, projectId),
19158
- eq32(gtmConnections.selectedAccountId, connection.selectedAccountId),
19159
- eq32(gtmConnections.selectedContainerId, connection.selectedContainerId),
19160
- connection.selectedWorkspaceId === null ? isNull7(gtmConnections.selectedWorkspaceId) : eq32(gtmConnections.selectedWorkspaceId, connection.selectedWorkspaceId),
19161
- eq32(gtmConnections.selectionGeneration, connection.selectionGeneration)
19283
+ }).where(and27(
19284
+ eq33(gtmConnections.id, connection.id),
19285
+ eq33(gtmConnections.projectId, projectId),
19286
+ eq33(gtmConnections.selectedAccountId, connection.selectedAccountId),
19287
+ eq33(gtmConnections.selectedContainerId, connection.selectedContainerId),
19288
+ connection.selectedWorkspaceId === null ? isNull7(gtmConnections.selectedWorkspaceId) : eq33(gtmConnections.selectedWorkspaceId, connection.selectedWorkspaceId),
19289
+ eq33(gtmConnections.selectionGeneration, connection.selectionGeneration)
19162
19290
  )).run();
19163
19291
  if (connectionClaim.changes !== 1) {
19164
19292
  throw withOutcomeReason(
@@ -19196,7 +19324,7 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19196
19324
  // src/server.ts
19197
19325
  var _require = createRequire(import.meta.url);
19198
19326
  var { version: PKG_VERSION } = _require("../package.json");
19199
- var log27 = createLogger("Server");
19327
+ var log28 = createLogger("Server");
19200
19328
  var runtimeStartupByServer = /* @__PURE__ */ new WeakMap();
19201
19329
  function waitForServerRuntimeStartup(app) {
19202
19330
  const startup = runtimeStartupByServer.get(app);
@@ -19446,7 +19574,7 @@ function applyLegacyCredentials(rows, config, persistConfigPatch = saveConfigPat
19446
19574
  }
19447
19575
  if (migratedGoogle > 0) {
19448
19576
  persistConfigPatch({ google: config.google });
19449
- log27.info("credentials.migrated", { type: "google", count: migratedGoogle });
19577
+ log28.info("credentials.migrated", { type: "google", count: migratedGoogle });
19450
19578
  }
19451
19579
  let migratedGa4 = 0;
19452
19580
  for (const row of rows.ga4) {
@@ -19464,7 +19592,7 @@ function applyLegacyCredentials(rows, config, persistConfigPatch = saveConfigPat
19464
19592
  }
19465
19593
  if (migratedGa4 > 0) {
19466
19594
  persistConfigPatch({ ga4: config.ga4 });
19467
- log27.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
19595
+ log28.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
19468
19596
  }
19469
19597
  }
19470
19598
  function isLoopbackPeerAddress(address) {
@@ -19747,17 +19875,17 @@ async function createServer(opts) {
19747
19875
  applyLegacyCredentials(legacyRows, opts.config, persistConfigPatch);
19748
19876
  dropLegacyCredentialColumns(opts.db);
19749
19877
  } catch (err) {
19750
- log27.warn("credentials.migration.failed", {
19878
+ log28.warn("credentials.migration.failed", {
19751
19879
  error: describeError(err)
19752
19880
  });
19753
19881
  }
19754
19882
  }
19755
- log27.info("providers.configured", {
19883
+ log28.info("providers.configured", {
19756
19884
  providers: registeredProviderNames({ providers, cdp: opts.config.cdp })
19757
19885
  });
19758
19886
  registry.replace(configuredProviderEntries(opts.config, API_ADAPTERS, cdpChatgptAdapter));
19759
19887
  for (const name of providersWithUnsupportedBatch(providers, (provider) => adapterMap[provider])) {
19760
- log27.warn("provider.batch.unsupported", {
19888
+ log28.warn("provider.batch.unsupported", {
19761
19889
  provider: name,
19762
19890
  message: `providers.${name}.batch.enabled is true, but the ${name} adapter has no batch API; its sweeps run sync.`
19763
19891
  });
@@ -19814,7 +19942,7 @@ async function createServer(opts) {
19814
19942
  async (ctx) => {
19815
19943
  if (!sessionRegistry) return;
19816
19944
  if (!agentProactive) return;
19817
- const project = opts.db.select({ name: projects.name }).from(projects).where(eq33(projects.id, ctx.projectId)).get();
19945
+ const project = opts.db.select({ name: projects.name }).from(projects).where(eq34(projects.id, ctx.projectId)).get();
19818
19946
  if (!project) return;
19819
19947
  let content;
19820
19948
  if (ctx.kind === RunKinds["aeo-discover-probe"]) {
@@ -20243,6 +20371,7 @@ async function createServer(opts) {
20243
20371
  };
20244
20372
  let stopSiteLiveness = null;
20245
20373
  let stopReviewDeliveries = null;
20374
+ let stopUnrecognizedAgentAlerts = null;
20246
20375
  let stopProviderBatchPoller = null;
20247
20376
  let stopInstallState = null;
20248
20377
  const scheduler = new Scheduler(opts.db, {
@@ -20286,7 +20415,7 @@ async function createServer(opts) {
20286
20415
  void (async () => {
20287
20416
  try {
20288
20417
  const report = await schedulerClient.runDoctor({ project: projectName, checkIds: scheduledHealthCheckIds() });
20289
- const project = opts.db.select().from(projects).where(eq33(projects.name, projectName)).get();
20418
+ const project = opts.db.select().from(projects).where(eq34(projects.name, projectName)).get();
20290
20419
  if (!project) {
20291
20420
  app.log.warn({ projectName }, "doctor schedule fired for an unknown project");
20292
20421
  return;
@@ -20578,7 +20707,7 @@ async function createServer(opts) {
20578
20707
  })();
20579
20708
  if (opts.config.apiKey) {
20580
20709
  const keyHash = hashApiKey(opts.config.apiKey);
20581
- const existing = opts.db.select().from(apiKeys).where(eq33(apiKeys.keyHash, keyHash)).get();
20710
+ const existing = opts.db.select().from(apiKeys).where(eq34(apiKeys.keyHash, keyHash)).get();
20582
20711
  if (!existing) {
20583
20712
  const prefix = opts.config.apiKey.slice(0, 12);
20584
20713
  opts.db.insert(apiKeys).values({
@@ -20597,14 +20726,14 @@ async function createServer(opts) {
20597
20726
  );
20598
20727
  const currentPasswordFingerprint = () => hashSessionToken(opts.config.dashboardPasswordHash ?? "");
20599
20728
  opts.db.delete(dashboardSessions).where(or4(
20600
- eq33(dashboardSessions.apiKeyHash, ""),
20601
- and27(
20729
+ eq34(dashboardSessions.apiKeyHash, ""),
20730
+ and28(
20602
20731
  isNotNull5(dashboardSessions.passwordFingerprint),
20603
20732
  ne4(dashboardSessions.passwordFingerprint, currentPasswordFingerprint())
20604
20733
  )
20605
20734
  )).run();
20606
20735
  if (!opts.config.dashboardPasswordHash) {
20607
- const lastPasswordEventAt = (action) => opts.db.select({ at: max(auditLog.createdAt) }).from(auditLog).where(and27(eq33(auditLog.entityType, "dashboard-password"), eq33(auditLog.action, action))).get()?.at ?? null;
20736
+ const lastPasswordEventAt = (action) => opts.db.select({ at: max(auditLog.createdAt) }).from(auditLog).where(and28(eq34(auditLog.entityType, "dashboard-password"), eq34(auditLog.action, action))).get()?.at ?? null;
20608
20737
  const createdAt = lastPasswordEventAt("dashboard-password.created");
20609
20738
  const clearedAt = lastPasswordEventAt("dashboard-password.cleared");
20610
20739
  if (createdAt !== null && (clearedAt === null || clearedAt < createdAt)) {
@@ -20638,23 +20767,23 @@ async function createServer(opts) {
20638
20767
  currentApiKeyHash: apiKeys.keyHash,
20639
20768
  passwordFingerprint: dashboardSessions.passwordFingerprint,
20640
20769
  expiresAt: dashboardSessions.expiresAt
20641
- }).from(dashboardSessions).innerJoin(apiKeys, eq33(apiKeys.id, dashboardSessions.apiKeyId)).where(and27(eq33(dashboardSessions.tokenHash, tokenHash), isNull8(apiKeys.revokedAt))).get();
20770
+ }).from(dashboardSessions).innerJoin(apiKeys, eq34(apiKeys.id, dashboardSessions.apiKeyId)).where(and28(eq34(dashboardSessions.tokenHash, tokenHash), isNull8(apiKeys.revokedAt))).get();
20642
20771
  if (!session) return null;
20643
20772
  const expiresAt = Date.parse(session.expiresAt);
20644
20773
  if (!Number.isFinite(expiresAt) || expiresAt <= Date.now() || session.apiKeyHash !== session.currentApiKeyHash || session.passwordFingerprint !== null && session.passwordFingerprint !== currentPasswordFingerprint()) {
20645
- opts.db.delete(dashboardSessions).where(eq33(dashboardSessions.tokenHash, tokenHash)).run();
20774
+ opts.db.delete(dashboardSessions).where(eq34(dashboardSessions.tokenHash, tokenHash)).run();
20646
20775
  return null;
20647
20776
  }
20648
20777
  return session.apiKeyId;
20649
20778
  };
20650
20779
  const clearSession = (sessionId) => {
20651
20780
  if (sessionId) {
20652
- opts.db.delete(dashboardSessions).where(eq33(dashboardSessions.tokenHash, hashSessionToken(sessionId))).run();
20781
+ opts.db.delete(dashboardSessions).where(eq34(dashboardSessions.tokenHash, hashSessionToken(sessionId))).run();
20653
20782
  }
20654
20783
  };
20655
20784
  const getDefaultApiKey = () => {
20656
20785
  if (!opts.config.apiKey) return void 0;
20657
- return opts.db.select().from(apiKeys).where(eq33(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
20786
+ return opts.db.select().from(apiKeys).where(eq34(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
20658
20787
  };
20659
20788
  const setSessionCookie = (reply, sessionId) => {
20660
20789
  reply.header(
@@ -20815,12 +20944,12 @@ async function createServer(opts) {
20815
20944
  return reply.send({ authenticated: true });
20816
20945
  }
20817
20946
  if (apiKey) {
20818
- const key = opts.db.select().from(apiKeys).where(eq33(apiKeys.keyHash, hashApiKey(apiKey))).get();
20947
+ const key = opts.db.select().from(apiKeys).where(eq34(apiKeys.keyHash, hashApiKey(apiKey))).get();
20819
20948
  if (!key || key.revokedAt) {
20820
20949
  const err2 = authInvalid();
20821
20950
  return reply.status(err2.statusCode).send(err2.toJSON());
20822
20951
  }
20823
- opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq33(apiKeys.id, key.id)).run();
20952
+ opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq34(apiKeys.id, key.id)).run();
20824
20953
  setSessionCookie(reply, createSession(key));
20825
20954
  return reply.send({ authenticated: true });
20826
20955
  }
@@ -20886,7 +21015,7 @@ async function createServer(opts) {
20886
21015
  const sentimentPoller = createSentimentPoller(
20887
21016
  () => sentimentWorker.tick(),
20888
21017
  () => {
20889
- log27.error("sentiment.worker-failed", { reason: "Stored sentiment worker could not finish its tick." });
21018
+ log28.error("sentiment.worker-failed", { reason: "Stored sentiment worker could not finish its tick." });
20890
21019
  }
20891
21020
  );
20892
21021
  const pollSentiment = sentimentPoller.poll;
@@ -20898,7 +21027,7 @@ async function createServer(opts) {
20898
21027
  const recomputeDebts = /* @__PURE__ */ new Map();
20899
21028
  let recomputeSequence = 0;
20900
21029
  const markAnswerFieldRecompute = (projectId, full) => {
20901
- opts.db.update(projects).set({ answerFieldsRecompute: full ? "full" : sql13`CASE WHEN ${projects.answerFieldsRecompute} = 'full' THEN 'full' ELSE 'competitors' END` }).where(eq33(projects.id, projectId)).run();
21030
+ opts.db.update(projects).set({ answerFieldsRecompute: full ? "full" : sql14`CASE WHEN ${projects.answerFieldsRecompute} = 'full' THEN 'full' ELSE 'competitors' END` }).where(eq34(projects.id, projectId)).run();
20902
21031
  };
20903
21032
  const requestAnswerFieldRecompute = (projectId, projectName, scope, reason) => {
20904
21033
  const sequence = ++recomputeSequence;
@@ -20916,10 +21045,10 @@ async function createServer(opts) {
20916
21045
  if (pass.full && debt.latestFull <= sequence) debt.latestFull = 0;
20917
21046
  try {
20918
21047
  if (debt.latest === sequence && debt.latestFull === 0) {
20919
- opts.db.update(projects).set({ answerFieldsRecompute: null }).where(eq33(projects.id, projectId)).run();
21048
+ opts.db.update(projects).set({ answerFieldsRecompute: null }).where(eq34(projects.id, projectId)).run();
20920
21049
  if (recomputeDebts.get(projectId) === debt) recomputeDebts.delete(projectId);
20921
21050
  } else if (debt.latestFull === 0) {
20922
- opts.db.update(projects).set({ answerFieldsRecompute: "competitors" }).where(and27(eq33(projects.id, projectId), eq33(projects.answerFieldsRecompute, "full"))).run();
21051
+ opts.db.update(projects).set({ answerFieldsRecompute: "competitors" }).where(and28(eq34(projects.id, projectId), eq34(projects.answerFieldsRecompute, "full"))).run();
20923
21052
  }
20924
21053
  } catch (err) {
20925
21054
  app.log.error({ err, projectId }, "could not clear the owed answer field recompute");
@@ -20956,7 +21085,7 @@ async function createServer(opts) {
20956
21085
  await yieldToEventLoop4();
20957
21086
  const reason = (competitorAutoAliasReasons.get(projectId) ?? []).join(",");
20958
21087
  competitorAutoAliasReasons.delete(projectId);
20959
- const project = opts.db.select().from(projects).where(eq33(projects.id, projectId)).get();
21088
+ const project = opts.db.select().from(projects).where(eq34(projects.id, projectId)).get();
20960
21089
  if (!project || project.competitorAutoAliases === "off") return;
20961
21090
  if (project.competitorAutoAliases === "apply") {
20962
21091
  await competitorAutoAliases.request(projectId, reason).catch(() => void 0);
@@ -21156,8 +21285,8 @@ async function createServer(opts) {
21156
21285
  getAgentPin: () => describeAgentPin(opts.config),
21157
21286
  getGoogleMarketingDoctorInput: (ctx) => {
21158
21287
  if (!ctx.project) return null;
21159
- const googleAds = opts.db.select().from(googleAdsConnections).where(eq33(googleAdsConnections.projectId, ctx.project.id)).get();
21160
- const gtm = opts.db.select().from(gtmConnections).where(eq33(gtmConnections.projectId, ctx.project.id)).get();
21288
+ const googleAds = opts.db.select().from(googleAdsConnections).where(eq34(googleAdsConnections.projectId, ctx.project.id)).get();
21289
+ const gtm = opts.db.select().from(gtmConnections).where(eq34(gtmConnections.projectId, ctx.project.id)).get();
21161
21290
  const googleAdsCredential = getGoogleAdsConnection(opts.config, ctx.project.id);
21162
21291
  const gtmCredential = getGtmConnection(opts.config, ctx.project.id);
21163
21292
  const googleAdsConnected = Boolean(googleAdsCredential?.accessToken);
@@ -21407,7 +21536,7 @@ async function createServer(opts) {
21407
21536
  ...inspectOpts,
21408
21537
  config: opts.config
21409
21538
  }).then(() => {
21410
- const finished = opts.db.select({ status: runs.status }).from(runs).where(eq33(runs.id, runId)).get();
21539
+ const finished = opts.db.select({ status: runs.status }).from(runs).where(eq34(runs.id, runId)).get();
21411
21540
  if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
21412
21541
  return maybeRefreshGscCoverage(
21413
21542
  opts.db,
@@ -21897,6 +22026,7 @@ async function createServer(opts) {
21897
22026
  notify: (projectId, result) => notifier.onSiteLivenessChecked(projectId, result)
21898
22027
  });
21899
22028
  stopReviewDeliveries = startReviewDeliveryLoop(() => notifier.drainDueReviewDeliveries());
22029
+ stopUnrecognizedAgentAlerts = startUnrecognizedAgentAlertLoop(() => notifier.dispatchUnrecognizedAgentAlerts());
21900
22030
  stopProviderBatchPoller = startProviderBatchPoller({ db: opts.db, registry, runner: jobRunner });
21901
22031
  if (!embed.enabled) {
21902
22032
  stopInstallState = startInstallStateTelemetry({
@@ -21906,7 +22036,7 @@ async function createServer(opts) {
21906
22036
  isBacklinksInstalled: () => isDuckdbInstalled()
21907
22037
  });
21908
22038
  }
21909
- for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq33(researchRuns.status, ResearchRunStatuses.queued)).all()) {
22039
+ for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq34(researchRuns.status, ResearchRunStatuses.queued)).all()) {
21910
22040
  dispatchResearchRun(run.id, run.projectId, OutcomeTriggers.startup);
21911
22041
  }
21912
22042
  runtimeStartupSettled = true;
@@ -21923,6 +22053,8 @@ async function createServer(opts) {
21923
22053
  stopSiteLiveness = null;
21924
22054
  stopReviewDeliveries?.();
21925
22055
  stopReviewDeliveries = null;
22056
+ stopUnrecognizedAgentAlerts?.();
22057
+ stopUnrecognizedAgentAlerts = null;
21926
22058
  stopProviderBatchPoller?.();
21927
22059
  stopProviderBatchPoller = null;
21928
22060
  stopInstallState?.();