hive-intelligence 1.1.3 → 1.3.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 (174) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +315 -85
  3. package/agent-skills/.claude-plugin/plugin.json +10 -4
  4. package/agent-skills/.mcp.json +8 -0
  5. package/agent-skills/AGENTS.md +37 -0
  6. package/agent-skills/README.md +28 -23
  7. package/agent-skills/SKILL.md +40 -10
  8. package/agent-skills/evals/trigger-queries.json +270 -0
  9. package/agent-skills/package.json +4 -4
  10. package/agent-skills/packs/hive-build/SKILL.md +54 -25
  11. package/agent-skills/packs/hive-build/evals/evals.json +16 -3
  12. package/agent-skills/packs/hive-build/evals/triggers.json +50 -0
  13. package/agent-skills/packs/hive-build-onboarding/SKILL.md +27 -34
  14. package/agent-skills/packs/hive-build-onboarding/evals/evals.json +1 -1
  15. package/agent-skills/packs/hive-build-onboarding/evals/triggers.json +50 -0
  16. package/agent-skills/packs/hive-cli/SKILL.md +33 -35
  17. package/agent-skills/packs/hive-cli/evals/evals.json +1 -1
  18. package/agent-skills/packs/hive-cli/evals/triggers.json +50 -0
  19. package/agent-skills/packs/hive-defi-research/SKILL.md +105 -19
  20. package/agent-skills/packs/hive-defi-research/evals/evals.json +6 -3
  21. package/agent-skills/packs/hive-defi-research/evals/triggers.json +50 -0
  22. package/agent-skills/packs/hive-dex-pool-analysis/SKILL.md +106 -16
  23. package/agent-skills/packs/hive-dex-pool-analysis/evals/evals.json +5 -3
  24. package/agent-skills/packs/hive-dex-pool-analysis/evals/triggers.json +50 -0
  25. package/agent-skills/packs/hive-market-research/SKILL.md +130 -21
  26. package/agent-skills/packs/hive-market-research/evals/evals.json +15 -2
  27. package/agent-skills/packs/hive-market-research/evals/triggers.json +50 -0
  28. package/agent-skills/packs/hive-mcp/SKILL.md +98 -153
  29. package/agent-skills/packs/hive-mcp/evals/evals.json +4 -3
  30. package/agent-skills/packs/hive-mcp/evals/triggers.json +50 -0
  31. package/agent-skills/packs/hive-mcp/references/client-install-matrix.md +62 -35
  32. package/agent-skills/packs/hive-mcp/references/clients.md +154 -0
  33. package/agent-skills/packs/hive-network-infrastructure/SKILL.md +105 -20
  34. package/agent-skills/packs/hive-network-infrastructure/evals/evals.json +4 -2
  35. package/agent-skills/packs/hive-network-infrastructure/evals/triggers.json +50 -0
  36. package/agent-skills/packs/hive-nft-research/SKILL.md +105 -18
  37. package/agent-skills/packs/hive-nft-research/evals/evals.json +4 -2
  38. package/agent-skills/packs/hive-nft-research/evals/triggers.json +50 -0
  39. package/agent-skills/packs/hive-prediction-markets/SKILL.md +108 -21
  40. package/agent-skills/packs/hive-prediction-markets/evals/evals.json +4 -2
  41. package/agent-skills/packs/hive-prediction-markets/evals/triggers.json +50 -0
  42. package/agent-skills/packs/hive-query/SKILL.md +80 -33
  43. package/agent-skills/packs/hive-query/evals/evals.json +7 -5
  44. package/agent-skills/packs/hive-query/evals/triggers.json +50 -0
  45. package/agent-skills/packs/hive-query/references/root-mcp-workflow.md +39 -18
  46. package/agent-skills/packs/hive-security-risk/SKILL.md +110 -19
  47. package/agent-skills/packs/hive-security-risk/evals/evals.json +4 -2
  48. package/agent-skills/packs/hive-security-risk/evals/triggers.json +50 -0
  49. package/agent-skills/packs/hive-solana-analysis/SKILL.md +115 -19
  50. package/agent-skills/packs/hive-solana-analysis/evals/evals.json +4 -2
  51. package/agent-skills/packs/hive-solana-analysis/evals/triggers.json +50 -0
  52. package/agent-skills/packs/hive-stateful-monitoring/SKILL.md +120 -0
  53. package/agent-skills/packs/hive-stateful-monitoring/evals/evals.json +54 -0
  54. package/agent-skills/packs/hive-stateful-monitoring/evals/triggers.json +50 -0
  55. package/agent-skills/packs/hive-stateful-monitoring/references/b2b-subject-context.md +53 -0
  56. package/agent-skills/packs/hive-token-diligence/SKILL.md +119 -21
  57. package/agent-skills/packs/hive-token-diligence/evals/evals.json +6 -4
  58. package/agent-skills/packs/hive-token-diligence/evals/triggers.json +50 -0
  59. package/agent-skills/packs/hive-tool-discovery/SKILL.md +48 -19
  60. package/agent-skills/packs/hive-tool-discovery/evals/evals.json +17 -3
  61. package/agent-skills/packs/hive-tool-discovery/evals/triggers.json +50 -0
  62. package/agent-skills/packs/hive-tool-discovery/references/discovery-surfaces.md +31 -6
  63. package/agent-skills/packs/hive-wallet-investigation/SKILL.md +111 -18
  64. package/agent-skills/packs/hive-wallet-investigation/evals/evals.json +5 -3
  65. package/agent-skills/packs/hive-wallet-investigation/evals/triggers.json +50 -0
  66. package/agent-skills/references/agents-routing.md +19 -7
  67. package/agent-skills/scripts/validate-agent-skills-package.mjs +116 -18
  68. package/agent-skills/skills.sh.json +3 -2
  69. package/bin/hive-mcp.js +5 -5
  70. package/build/{alias-CZ3DEYHF.js → alias-BKAVOYQV.js} +2 -3
  71. package/build/{api-client-I23XLSHU.js → api-client-FTVDFW5V.js} +4 -4
  72. package/build/{auth-KKINNBXT.js → auth-EZFJ5RYB.js} +3 -4
  73. package/build/{browser-auth-EEFGRHWY.js → browser-auth-OJSUNFBI.js} +2 -3
  74. package/build/{cache-EGNMENNA.js → cache-TBDQ7QBU.js} +2 -3
  75. package/build/{chunk-V77LO4RO.js → chunk-6PC3HUAK.js} +10 -4
  76. package/build/{chunk-XYBEFFZ3.js → chunk-725R3KLJ.js} +26 -4
  77. package/build/{chunk-PGBVNMNZ.js → chunk-7GD2MFYI.js} +57 -7
  78. package/build/{chunk-IDZD2NVG.js → chunk-AEU43ACS.js} +33 -10
  79. package/build/chunk-EPF36Q3Z.js +71 -0
  80. package/build/chunk-I5Z6FZRL.js +178 -0
  81. package/build/{chunk-KBVPIZS4.js → chunk-ILVPOLA7.js} +12 -4
  82. package/build/{chunk-B6G222LS.js → chunk-L326MQZP.js} +22 -6
  83. package/build/{chunk-QNCI4DEB.js → chunk-M6REON2I.js} +35 -21
  84. package/build/chunk-N32UIHBU.js +125 -0
  85. package/build/{chunk-IZTN7WFH.js → chunk-Q7VBYLX7.js} +12 -3
  86. package/build/chunk-QDZ7MAVN.js +447 -0
  87. package/build/{chunk-KGYBTXIN.js → chunk-R6YZTFQL.js} +2 -1
  88. package/build/{chunk-DYJBTKNR.js → chunk-RU6QOFHH.js} +9 -10
  89. package/build/{chunk-RKICZ6DZ.js → chunk-RUHO24SX.js} +16 -10
  90. package/build/{chunk-MPIUUE3T.js → chunk-TNQ7GJ6Q.js} +0 -60
  91. package/build/{chunk-2X3U4DFK.js → chunk-W2QL7LYI.js} +43586 -21498
  92. package/build/chunk-ZXKFJQDE.js +20 -0
  93. package/build/cli.js +3342 -3410
  94. package/build/{completion-BELZL7ND.js → completion-7KBW243H.js} +4 -5
  95. package/build/{config-TDX4VUGW.js → config-KAEUX6RM.js} +23 -4
  96. package/build/{config-dir-V43W2WIA.js → config-dir-5IH7MOOT.js} +2 -3
  97. package/build/{doctor-5K5UWHNX.js → doctor-ORDOCGLM.js} +34 -10
  98. package/build/{errors-E6MVUBNA.js → errors-HGHUF575.js} +2 -3
  99. package/build/{format-TSFSI2K7.js → format-H7FOQSYY.js} +1 -2
  100. package/build/{init-YQ5NBICF.js → init-UCTCUYFA.js} +52 -9
  101. package/build/init-all-BVWW3JVK.js +408 -0
  102. package/build/mcpServer-RUEOTTF7.js +34 -0
  103. package/build/monitor-worker.js +1396 -0
  104. package/build/{namespace-PVHBFDW2.js → namespace-L5DGCY7K.js} +2 -3
  105. package/build/{open-YUIJP37F.js → open-V2RNA3QT.js} +2 -3
  106. package/build/{output-IYJBFCYK.js → output-ITGLBKIG.js} +2 -3
  107. package/build/release.json +8 -0
  108. package/build/{serve-KWNJK6EY.js → serve-DKLR2WQH.js} +9 -4
  109. package/build/server.js +5399 -2945
  110. package/build/{setup-YTUKMPNP.js → setup-PTYFMTK5.js} +60 -56
  111. package/build/stdio.js +10 -8
  112. package/build/{telemetry-5IPRKZBL.js → telemetry-HPLDYR7U.js} +5 -4
  113. package/build/{tools-PJHWFCAG.js → tools-I7GLWTV6.js} +15 -15
  114. package/build/{uninstall-HWXEIP6K.js → uninstall-SAOIPL6K.js} +19 -11
  115. package/build/update-check-IEXPOYYR.js +18 -0
  116. package/build/upgrade-J4HIGCKE.js +171 -0
  117. package/build/version-Z4HJL4VV.js +16 -0
  118. package/build/{watch-KZ5EDZK3.js → watch-WVR4CGCD.js} +8 -7
  119. package/package.json +82 -41
  120. package/agent-skills/packs/hive-defi-research/references/workflow.md +0 -41
  121. package/agent-skills/packs/hive-dex-pool-analysis/references/workflow.md +0 -42
  122. package/agent-skills/packs/hive-market-research/references/workflow.md +0 -47
  123. package/agent-skills/packs/hive-network-infrastructure/references/workflow.md +0 -41
  124. package/agent-skills/packs/hive-nft-research/references/workflow.md +0 -42
  125. package/agent-skills/packs/hive-prediction-markets/references/workflow.md +0 -42
  126. package/agent-skills/packs/hive-security-risk/references/workflow.md +0 -46
  127. package/agent-skills/packs/hive-solana-analysis/references/workflow.md +0 -42
  128. package/agent-skills/packs/hive-token-diligence/references/workflow.md +0 -45
  129. package/agent-skills/packs/hive-wallet-investigation/references/workflow.md +0 -45
  130. package/build/alias-CZ3DEYHF.js.map +0 -1
  131. package/build/api-client-I23XLSHU.js.map +0 -1
  132. package/build/auth-KKINNBXT.js.map +0 -1
  133. package/build/browser-auth-EEFGRHWY.js.map +0 -1
  134. package/build/cache-EGNMENNA.js.map +0 -1
  135. package/build/chunk-2MYMHMBF.js +0 -340
  136. package/build/chunk-2MYMHMBF.js.map +0 -1
  137. package/build/chunk-2X3U4DFK.js.map +0 -1
  138. package/build/chunk-B6G222LS.js.map +0 -1
  139. package/build/chunk-DYJBTKNR.js.map +0 -1
  140. package/build/chunk-IDZD2NVG.js.map +0 -1
  141. package/build/chunk-IZTN7WFH.js.map +0 -1
  142. package/build/chunk-KBVPIZS4.js.map +0 -1
  143. package/build/chunk-KGYBTXIN.js.map +0 -1
  144. package/build/chunk-MPIUUE3T.js.map +0 -1
  145. package/build/chunk-PGBVNMNZ.js.map +0 -1
  146. package/build/chunk-QNCI4DEB.js.map +0 -1
  147. package/build/chunk-RKICZ6DZ.js.map +0 -1
  148. package/build/chunk-UKHZIVDT.js +0 -44
  149. package/build/chunk-UKHZIVDT.js.map +0 -1
  150. package/build/chunk-V77LO4RO.js.map +0 -1
  151. package/build/chunk-XYBEFFZ3.js.map +0 -1
  152. package/build/cli.js.map +0 -1
  153. package/build/completion-BELZL7ND.js.map +0 -1
  154. package/build/config-TDX4VUGW.js.map +0 -1
  155. package/build/config-dir-V43W2WIA.js.map +0 -1
  156. package/build/doctor-5K5UWHNX.js.map +0 -1
  157. package/build/errors-E6MVUBNA.js.map +0 -1
  158. package/build/format-TSFSI2K7.js.map +0 -1
  159. package/build/init-YQ5NBICF.js.map +0 -1
  160. package/build/init-all-6OWEQALB.js +0 -274
  161. package/build/init-all-6OWEQALB.js.map +0 -1
  162. package/build/mcpServer-AEX3RQ4U.js +0 -20
  163. package/build/mcpServer-AEX3RQ4U.js.map +0 -1
  164. package/build/namespace-PVHBFDW2.js.map +0 -1
  165. package/build/open-YUIJP37F.js.map +0 -1
  166. package/build/output-IYJBFCYK.js.map +0 -1
  167. package/build/serve-KWNJK6EY.js.map +0 -1
  168. package/build/server.js.map +0 -1
  169. package/build/setup-YTUKMPNP.js.map +0 -1
  170. package/build/stdio.js.map +0 -1
  171. package/build/telemetry-5IPRKZBL.js.map +0 -1
  172. package/build/tools-PJHWFCAG.js.map +0 -1
  173. package/build/uninstall-HWXEIP6K.js.map +0 -1
  174. package/build/watch-KZ5EDZK3.js.map +0 -1
@@ -0,0 +1,1396 @@
1
+ import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
2
+ import {
3
+ consumeCredits,
4
+ executeToolByName
5
+ } from "./chunk-I5Z6FZRL.js";
6
+ import {
7
+ HyperliquidAPI,
8
+ MIN_MONITOR_CADENCE_MS,
9
+ closeRedisClient,
10
+ collectRwaRows,
11
+ getEnvVar,
12
+ getSupabaseAdmin,
13
+ isManualMonitorCadence,
14
+ isSupabaseConfigured,
15
+ monitorCadenceIntervalMs,
16
+ runScheduledHealthCanaries,
17
+ secureLogger
18
+ } from "./chunk-W2QL7LYI.js";
19
+ import "./chunk-L326MQZP.js";
20
+ import "./chunk-EPF36Q3Z.js";
21
+ import "./chunk-ZXKFJQDE.js";
22
+ import "./chunk-ILVPOLA7.js";
23
+
24
+ // src/monitor-worker.ts
25
+ import { resolve } from "path";
26
+ import { fileURLToPath } from "url";
27
+
28
+ // src/services/monitor-runner.ts
29
+ import { createHash } from "crypto";
30
+ var DEFAULT_BATCH_SIZE = 10;
31
+ var DEFAULT_LOCK_TIMEOUT_SECONDS = 300;
32
+ var DEFAULT_WORKER_ID = `hive-monitor-worker-${process.pid}`;
33
+ function isRecord(value) {
34
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
35
+ }
36
+ function stringValue(value) {
37
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
38
+ }
39
+ function numberValue(value) {
40
+ if (typeof value === "number" && Number.isFinite(value)) return value;
41
+ if (typeof value === "string" && value.trim()) {
42
+ const parsed = Number(value);
43
+ return Number.isFinite(parsed) ? parsed : void 0;
44
+ }
45
+ return void 0;
46
+ }
47
+ function booleanValue(value) {
48
+ if (typeof value === "boolean") return value;
49
+ if (typeof value === "string") {
50
+ const normalized = value.trim().toLowerCase();
51
+ if (["true", "1", "yes"].includes(normalized)) return true;
52
+ if (["false", "0", "no"].includes(normalized)) return false;
53
+ }
54
+ return void 0;
55
+ }
56
+ function recordArray(value) {
57
+ if (!Array.isArray(value)) return [];
58
+ return value.slice(0, 5).map((item) => {
59
+ if (isRecord(item)) return item;
60
+ if (typeof item === "string" && item.trim()) return { id: item.trim() };
61
+ return null;
62
+ }).filter((item) => Boolean(item));
63
+ }
64
+ function stringArray(value) {
65
+ if (!Array.isArray(value)) return [];
66
+ return value.map((item) => typeof item === "string" ? item.trim() : void 0).filter((item) => Boolean(item)).slice(0, 5);
67
+ }
68
+ function normalizeAlchemyNetwork(target) {
69
+ const raw = stringValue(target.network) ?? stringValue(target.chain) ?? "ethereum";
70
+ const normalized = raw.toLowerCase();
71
+ const aliases = {
72
+ eth: "eth-mainnet",
73
+ ethereum: "eth-mainnet",
74
+ mainnet: "eth-mainnet",
75
+ base: "base-mainnet",
76
+ polygon: "polygon-mainnet",
77
+ matic: "polygon-mainnet",
78
+ arbitrum: "arb-mainnet",
79
+ arb: "arb-mainnet",
80
+ optimism: "opt-mainnet",
81
+ opt: "opt-mainnet",
82
+ zksync: "zksync-mainnet",
83
+ scroll: "scroll-mainnet",
84
+ linea: "linea-mainnet",
85
+ worldchain: "worldchain-mainnet"
86
+ };
87
+ return aliases[normalized] ?? raw;
88
+ }
89
+ function normalizeNetworkId(target) {
90
+ const explicit = numberValue(target.networkId) ?? numberValue(target.network_id) ?? numberValue(target.chainId) ?? numberValue(target.chain_id);
91
+ if (explicit) return explicit;
92
+ const raw = stringValue(target.network) ?? stringValue(target.chain) ?? "ethereum";
93
+ const aliases = {
94
+ eth: 1,
95
+ ethereum: 1,
96
+ mainnet: 1,
97
+ base: 8453,
98
+ polygon: 137,
99
+ matic: 137,
100
+ arbitrum: 42161,
101
+ arb: 42161,
102
+ optimism: 10,
103
+ opt: 10,
104
+ bsc: 56,
105
+ binance: 56,
106
+ avalanche: 43114,
107
+ avax: 43114,
108
+ linea: 59144,
109
+ scroll: 534352,
110
+ zksync: 324
111
+ };
112
+ return aliases[raw.toLowerCase()] ?? 1;
113
+ }
114
+ function isSolanaWalletTarget(target) {
115
+ const raw = stringValue(target.network) ?? stringValue(target.chain);
116
+ return ["sol", "solana", "svm", "solana-mainnet"].includes(
117
+ raw?.toLowerCase() ?? ""
118
+ );
119
+ }
120
+ function getMonitorTargetAddress(target) {
121
+ return stringValue(target.address) ?? stringValue(target.wallet) ?? stringValue(target.wallet_address) ?? stringValue(target.token_address) ?? stringValue(target.contract);
122
+ }
123
+ function buildWalletWorkflowPlan(monitor) {
124
+ const address = getMonitorTargetAddress(monitor.target);
125
+ if (!address) {
126
+ throw new Error("Wallet monitor target must include address.");
127
+ }
128
+ if (isSolanaWalletTarget(monitor.target)) {
129
+ return [
130
+ {
131
+ toolName: "helius_get_wallet_balances",
132
+ snapshotKey: "wallet_balances",
133
+ args: {
134
+ wallet: address,
135
+ limit: 100,
136
+ showNative: true,
137
+ showNfts: false
138
+ }
139
+ },
140
+ {
141
+ toolName: "helius_get_wallet_transfers",
142
+ snapshotKey: "wallet_transfers",
143
+ args: {
144
+ wallet: address,
145
+ limit: 25
146
+ }
147
+ },
148
+ {
149
+ toolName: "helius_get_wallet_history",
150
+ snapshotKey: "wallet_history",
151
+ args: {
152
+ wallet: address,
153
+ limit: 25
154
+ }
155
+ }
156
+ ];
157
+ }
158
+ const network = normalizeAlchemyNetwork(monitor.target);
159
+ const transferCategories = ["external", "erc20"];
160
+ const fromBlock = stringValue(monitor.target.from_block) ?? "0x0";
161
+ return [
162
+ {
163
+ toolName: "alchemy_get_token_balances_by_wallet",
164
+ snapshotKey: "token_balances",
165
+ args: {
166
+ address,
167
+ network,
168
+ includeNativeTokens: true,
169
+ includeErc20Tokens: true
170
+ }
171
+ },
172
+ {
173
+ toolName: "alchemy_get_native_balance",
174
+ snapshotKey: "native_balance",
175
+ args: { address, network }
176
+ },
177
+ {
178
+ toolName: "alchemy_get_asset_transfers",
179
+ snapshotKey: "outgoing_transfers",
180
+ args: {
181
+ network,
182
+ fromAddress: address,
183
+ fromBlock,
184
+ toBlock: "latest",
185
+ category: transferCategories,
186
+ maxCount: "0x5"
187
+ }
188
+ },
189
+ {
190
+ toolName: "alchemy_get_asset_transfers",
191
+ snapshotKey: "incoming_transfers",
192
+ args: {
193
+ network,
194
+ toAddress: address,
195
+ fromBlock,
196
+ toBlock: "latest",
197
+ category: transferCategories,
198
+ maxCount: "0x5"
199
+ }
200
+ }
201
+ ];
202
+ }
203
+ function buildTokenWorkflowPlan(monitor) {
204
+ const address = getMonitorTargetAddress(monitor.target);
205
+ if (!address) {
206
+ throw new Error("Token monitor target must include address.");
207
+ }
208
+ const network = normalizeAlchemyNetwork(monitor.target);
209
+ const networkId = normalizeNetworkId(monitor.target);
210
+ return [
211
+ {
212
+ toolName: "alchemy_get_token_metadata",
213
+ snapshotKey: "metadata",
214
+ args: { network, address }
215
+ },
216
+ {
217
+ toolName: "alchemy_get_token_prices_by_address",
218
+ snapshotKey: "alchemy_price",
219
+ args: { network, address }
220
+ },
221
+ {
222
+ toolName: "get_token_security",
223
+ snapshotKey: "security",
224
+ args: {
225
+ chainId: String(networkId),
226
+ contract_addresses: address
227
+ }
228
+ },
229
+ {
230
+ toolName: "codex_get_token_price",
231
+ snapshotKey: "dex_price",
232
+ args: { inputs: [{ networkId, address }] }
233
+ },
234
+ {
235
+ toolName: "get_token_holders",
236
+ snapshotKey: "holders",
237
+ args: { networkId, address }
238
+ }
239
+ ];
240
+ }
241
+ function buildProtocolWorkflowPlan(monitor) {
242
+ const protocol = stringValue(monitor.target.protocol) ?? stringValue(monitor.target.slug) ?? stringValue(monitor.target.name);
243
+ if (!protocol) {
244
+ throw new Error("Protocol monitor target must include protocol.");
245
+ }
246
+ return [
247
+ {
248
+ toolName: "get_protocol_tvl",
249
+ snapshotKey: "tvl",
250
+ args: { protocol }
251
+ },
252
+ {
253
+ toolName: "get_protocol_fees",
254
+ snapshotKey: "fees",
255
+ args: { protocol }
256
+ },
257
+ {
258
+ toolName: "get_protocol_fee_summary",
259
+ snapshotKey: "fee_summary",
260
+ args: { protocol }
261
+ },
262
+ {
263
+ toolName: "get_global_defi",
264
+ snapshotKey: "global_defi",
265
+ args: {}
266
+ }
267
+ ];
268
+ }
269
+ function buildMarketWorkflowPlan(monitor) {
270
+ const ids = stringValue(monitor.target.ids) ?? stringValue(monitor.target.id) ?? stringValue(monitor.target.coin_id) ?? stringValue(monitor.target.symbol) ?? stringValue(monitor.target.name);
271
+ if (!ids) {
272
+ throw new Error(
273
+ "Market monitor target must include ids, id, coin_id, symbol, or name."
274
+ );
275
+ }
276
+ const vsCurrencies = stringValue(monitor.target.vs_currencies) ?? stringValue(monitor.target.vs_currency) ?? "usd";
277
+ const calls = [
278
+ {
279
+ toolName: "get_price",
280
+ snapshotKey: "price",
281
+ args: {
282
+ ids,
283
+ vs_currencies: vsCurrencies,
284
+ include_market_cap: true,
285
+ include_24hr_vol: true,
286
+ include_24hr_change: true,
287
+ include_last_updated_at: true
288
+ }
289
+ }
290
+ ];
291
+ if (booleanValue(monitor.target.include_trending_pools) === true) {
292
+ calls.push({
293
+ toolName: "get_trending_pools",
294
+ snapshotKey: "trending_pools",
295
+ args: {
296
+ duration: stringValue(monitor.target.trending_duration) ?? "24h",
297
+ include: "base_token,quote_token,dex,network",
298
+ page: numberValue(monitor.target.trending_page) ?? 1
299
+ }
300
+ });
301
+ }
302
+ return calls;
303
+ }
304
+ function buildPredictionMarketWorkflowPlan(monitor) {
305
+ const marketId = stringValue(monitor.target.marketId) ?? stringValue(monitor.target.market_id) ?? stringValue(monitor.target.id);
306
+ if (marketId) {
307
+ return [
308
+ {
309
+ toolName: "codex_prediction_market_price",
310
+ snapshotKey: "market_price",
311
+ args: { marketId }
312
+ },
313
+ {
314
+ toolName: "codex_prediction_market_stats",
315
+ snapshotKey: "market_stats",
316
+ args: {
317
+ marketId,
318
+ bucketCount: numberValue(monitor.target.bucket_count) ?? 6,
319
+ durations: ["hour1", "day1"]
320
+ }
321
+ }
322
+ ];
323
+ }
324
+ const phrase = stringValue(monitor.target.phrase) ?? stringValue(monitor.target.query) ?? stringValue(monitor.target.title);
325
+ if (!phrase) {
326
+ throw new Error(
327
+ "Prediction-market monitor target must include marketId or phrase."
328
+ );
329
+ }
330
+ return [
331
+ {
332
+ toolName: "codex_filter_prediction_markets",
333
+ snapshotKey: "market_search",
334
+ args: {
335
+ phrase,
336
+ limit: numberValue(monitor.target.limit) ?? 10,
337
+ offset: numberValue(monitor.target.offset) ?? 0
338
+ }
339
+ }
340
+ ];
341
+ }
342
+ function buildWatchlistDigestWorkflowPlan(monitor) {
343
+ const calls = [];
344
+ recordArray(monitor.target.wallets).slice(0, 2).forEach((wallet, index) => {
345
+ const target = { ...wallet };
346
+ if (!getMonitorTargetAddress(target) && typeof wallet.id === "string") {
347
+ target.address = wallet.id;
348
+ }
349
+ buildWalletWorkflowPlan({ target }).slice(0, 2).forEach((call) => {
350
+ calls.push({
351
+ ...call,
352
+ snapshotKey: `wallet_${index}_${call.snapshotKey}`
353
+ });
354
+ });
355
+ });
356
+ recordArray(monitor.target.tokens).slice(0, 2).forEach((token, index) => {
357
+ const target = { ...token };
358
+ if (!getMonitorTargetAddress(target) && typeof token.id === "string") {
359
+ target.address = token.id;
360
+ }
361
+ buildTokenWorkflowPlan({ target }).slice(0, 3).forEach((call) => {
362
+ calls.push({
363
+ ...call,
364
+ snapshotKey: `token_${index}_${call.snapshotKey}`
365
+ });
366
+ });
367
+ });
368
+ recordArray(monitor.target.protocols).slice(0, 2).forEach((protocol, index) => {
369
+ const target = { ...protocol };
370
+ if (!stringValue(target.protocol) && typeof protocol.id === "string") {
371
+ target.protocol = protocol.id;
372
+ }
373
+ buildProtocolWorkflowPlan({ target }).slice(0, 2).forEach((call) => {
374
+ calls.push({
375
+ ...call,
376
+ snapshotKey: `protocol_${index}_${call.snapshotKey}`
377
+ });
378
+ });
379
+ });
380
+ recordArray(monitor.target.markets).slice(0, 2).forEach((market, index) => {
381
+ const target = { ...market };
382
+ if (!stringValue(target.ids) && typeof market.id === "string") {
383
+ target.ids = market.id;
384
+ }
385
+ buildMarketWorkflowPlan({ target }).slice(0, 1).forEach((call) => {
386
+ calls.push({
387
+ ...call,
388
+ snapshotKey: `market_${index}_${call.snapshotKey}`
389
+ });
390
+ });
391
+ });
392
+ recordArray(monitor.target.prediction_markets).slice(0, 2).forEach((predictionMarket, index) => {
393
+ const target = { ...predictionMarket };
394
+ if (!stringValue(target.marketId) && typeof predictionMarket.id === "string") {
395
+ target.marketId = predictionMarket.id;
396
+ }
397
+ buildPredictionMarketWorkflowPlan({ target }).slice(0, 1).forEach((call) => {
398
+ calls.push({
399
+ ...call,
400
+ snapshotKey: `prediction_market_${index}_${call.snapshotKey}`
401
+ });
402
+ });
403
+ });
404
+ if (calls.length === 0) {
405
+ throw new Error(
406
+ "Watchlist digest target must include at least one wallet, token, protocol, market, or prediction_market."
407
+ );
408
+ }
409
+ return calls.slice(0, 20);
410
+ }
411
+ function buildTokenDiscoveryRiskWorkflowPlan(monitor) {
412
+ const calls = [];
413
+ const networks = stringArray(monitor.target.networks);
414
+ const include = "base_token,quote_token,dex,network";
415
+ if (networks.length === 0) {
416
+ calls.push({
417
+ toolName: "get_new_pools",
418
+ snapshotKey: "new_pools",
419
+ args: { include, page: numberValue(monitor.target.page) ?? 1 }
420
+ });
421
+ } else {
422
+ networks.slice(0, 3).forEach((network, index) => {
423
+ calls.push({
424
+ toolName: "get_new_pools_by_network",
425
+ snapshotKey: `new_pools_${index}`,
426
+ args: { network, include: "base_token,quote_token,dex", page: 1 }
427
+ });
428
+ });
429
+ }
430
+ calls.push({
431
+ toolName: "get_new_coins",
432
+ snapshotKey: "new_coins",
433
+ args: {}
434
+ });
435
+ stringArray(monitor.target.contracts ?? monitor.target.addresses).slice(0, 3).forEach((address, index) => {
436
+ const chainId = String(normalizeNetworkId(monitor.target));
437
+ calls.push({
438
+ toolName: "get_token_security",
439
+ snapshotKey: `token_security_${index}`,
440
+ args: { chainId, contract_addresses: address }
441
+ });
442
+ });
443
+ return calls;
444
+ }
445
+ function buildRiskWatchWorkflowPlan(monitor) {
446
+ const chainId = String(normalizeNetworkId(monitor.target));
447
+ const calls = [];
448
+ stringArray(monitor.target.addresses).slice(0, 5).forEach((address, index) => {
449
+ calls.push({
450
+ toolName: "check_malicious_address",
451
+ snapshotKey: `address_risk_${index}`,
452
+ args: { chainId, address }
453
+ });
454
+ calls.push({
455
+ toolName: "get_wallet_approvals",
456
+ snapshotKey: `wallet_approvals_${index}`,
457
+ args: { chainId, address }
458
+ });
459
+ });
460
+ stringArray(monitor.target.contracts).slice(0, 5).forEach((address, index) => {
461
+ calls.push({
462
+ toolName: "get_token_security",
463
+ snapshotKey: `contract_security_${index}`,
464
+ args: { chainId, contract_addresses: address }
465
+ });
466
+ calls.push({
467
+ toolName: "detect_rugpull",
468
+ snapshotKey: `rugpull_${index}`,
469
+ args: { chain_id: chainId, contract_address: address }
470
+ });
471
+ });
472
+ if (calls.length === 0) {
473
+ throw new Error("Risk-watch target must include addresses or contracts.");
474
+ }
475
+ return calls.slice(0, 20);
476
+ }
477
+ function buildMonitorWorkflowDefinition(monitor) {
478
+ switch (monitor.kind) {
479
+ case "wallet":
480
+ return {
481
+ type: "wallet",
482
+ label: "Wallet",
483
+ target: {
484
+ ...monitor.target,
485
+ address: getMonitorTargetAddress(monitor.target),
486
+ network: isSolanaWalletTarget(monitor.target) ? "solana" : normalizeAlchemyNetwork(monitor.target)
487
+ },
488
+ calls: buildWalletWorkflowPlan(monitor)
489
+ };
490
+ case "token":
491
+ return {
492
+ type: "token",
493
+ label: "Token",
494
+ target: {
495
+ ...monitor.target,
496
+ address: getMonitorTargetAddress(monitor.target),
497
+ network: normalizeAlchemyNetwork(monitor.target),
498
+ networkId: normalizeNetworkId(monitor.target)
499
+ },
500
+ calls: buildTokenWorkflowPlan(monitor)
501
+ };
502
+ case "protocol": {
503
+ const protocol = stringValue(monitor.target.protocol) ?? stringValue(monitor.target.slug) ?? stringValue(monitor.target.name);
504
+ return {
505
+ type: "protocol",
506
+ label: "Protocol",
507
+ target: { ...monitor.target, protocol },
508
+ calls: buildProtocolWorkflowPlan(monitor)
509
+ };
510
+ }
511
+ case "market": {
512
+ const ids = stringValue(monitor.target.ids) ?? stringValue(monitor.target.id) ?? stringValue(monitor.target.coin_id) ?? stringValue(monitor.target.symbol) ?? stringValue(monitor.target.name);
513
+ return {
514
+ type: "market",
515
+ label: "Market",
516
+ target: {
517
+ ...monitor.target,
518
+ ids,
519
+ vs_currencies: stringValue(monitor.target.vs_currencies) ?? stringValue(monitor.target.vs_currency) ?? "usd"
520
+ },
521
+ calls: buildMarketWorkflowPlan(monitor)
522
+ };
523
+ }
524
+ case "prediction_market":
525
+ return {
526
+ type: "prediction_market",
527
+ label: "Prediction market",
528
+ target: monitor.target,
529
+ calls: buildPredictionMarketWorkflowPlan(monitor)
530
+ };
531
+ case "watchlist_digest":
532
+ return {
533
+ type: "watchlist_digest",
534
+ label: "Watchlist digest",
535
+ target: monitor.target,
536
+ calls: buildWatchlistDigestWorkflowPlan(monitor)
537
+ };
538
+ case "token_discovery_risk":
539
+ return {
540
+ type: "token_discovery_risk",
541
+ label: "Token discovery risk",
542
+ target: monitor.target,
543
+ calls: buildTokenDiscoveryRiskWorkflowPlan(monitor)
544
+ };
545
+ case "risk_watch":
546
+ return {
547
+ type: "risk_watch",
548
+ label: "Risk watch",
549
+ target: monitor.target,
550
+ calls: buildRiskWatchWorkflowPlan(monitor)
551
+ };
552
+ }
553
+ }
554
+ function sortedJson(value) {
555
+ if (Array.isArray(value)) return `[${value.map(sortedJson).join(",")}]`;
556
+ if (value && typeof value === "object") {
557
+ return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${sortedJson(item)}`).join(",")}}`;
558
+ }
559
+ return JSON.stringify(value);
560
+ }
561
+ function stripVolatileFields(value) {
562
+ if (Array.isArray(value)) return value.map(stripVolatileFields);
563
+ if (!isRecord(value)) return value;
564
+ const omitted = /* @__PURE__ */ new Set([
565
+ "_hive",
566
+ "duration_ms",
567
+ "fetched_at",
568
+ "captured_at",
569
+ "created_at",
570
+ "updated_at",
571
+ "cache_status",
572
+ "request_id"
573
+ ]);
574
+ return Object.fromEntries(
575
+ Object.entries(value).filter(([key]) => !omitted.has(key)).map(([key, item]) => [key, stripVolatileFields(item)])
576
+ );
577
+ }
578
+ function compactForStorage(value, depth = 0) {
579
+ if (depth > 8) return "[max-depth]";
580
+ if (typeof value === "string") {
581
+ return value.length > 5e3 ? `${value.slice(0, 5e3)}...[truncated]` : value;
582
+ }
583
+ if (Array.isArray(value)) {
584
+ const items = value.slice(0, 50).map((item) => compactForStorage(item, depth + 1));
585
+ if (value.length > items.length) {
586
+ items.push({ _truncated_items: value.length - items.length });
587
+ }
588
+ return items;
589
+ }
590
+ if (isRecord(value)) {
591
+ return Object.fromEntries(
592
+ Object.entries(value).map(([key, item]) => [
593
+ key,
594
+ compactForStorage(item, depth + 1)
595
+ ])
596
+ );
597
+ }
598
+ return value;
599
+ }
600
+ function fingerprintSnapshot(snapshot) {
601
+ return createHash("sha256").update(sortedJson(stripVolatileFields(snapshot))).digest("hex");
602
+ }
603
+ function computeNextRunAt(cadence, from = /* @__PURE__ */ new Date()) {
604
+ const normalized = (cadence ?? "manual").trim().toLowerCase();
605
+ if (isManualMonitorCadence(normalized)) return null;
606
+ const intervalMs = monitorCadenceIntervalMs(normalized);
607
+ if (intervalMs === null) return null;
608
+ return new Date(
609
+ from.getTime() + Math.max(intervalMs, MIN_MONITOR_CADENCE_MS)
610
+ ).toISOString();
611
+ }
612
+ async function defaultConsumeCredit(args) {
613
+ return consumeCredits({
614
+ userId: args.userId,
615
+ cost: 1,
616
+ idempotencyKey: `monitor-run:${args.runId}`,
617
+ requestId: args.runId
618
+ });
619
+ }
620
+ async function debitMonitorRunCredit(args) {
621
+ try {
622
+ const debit = await args.consumeCredit({
623
+ userId: args.monitor.user_id,
624
+ runId: args.runId
625
+ });
626
+ if (debit.code === "insufficient" || debit.code === "no_wallet") {
627
+ return {
628
+ ok: false,
629
+ code: "MONITOR_CREDIT_EXHAUSTED",
630
+ message: "Monitor run skipped because the account has no available credits."
631
+ };
632
+ }
633
+ return { ok: true };
634
+ } catch (error) {
635
+ secureLogger.warn("Monitor credit debit failed", {
636
+ monitorId: args.monitor.id,
637
+ error: error instanceof Error ? error.message : String(error)
638
+ });
639
+ return {
640
+ ok: false,
641
+ code: "MONITOR_CREDIT_UNAVAILABLE",
642
+ message: "Monitor run skipped because the credit service is temporarily unavailable."
643
+ };
644
+ }
645
+ }
646
+ function hasAlertRule(monitor) {
647
+ return monitor.rules.some(
648
+ (rule) => isRecord(rule) && (rule.alert === true || rule.notify === true || [
649
+ "any_change",
650
+ "wallet_change",
651
+ "large_transfer",
652
+ "token_change",
653
+ "protocol_change",
654
+ "market_change",
655
+ "prediction_market_change"
656
+ ].includes(String(rule.type ?? "")))
657
+ );
658
+ }
659
+ var RULE_SEVERITY_ORDER = {
660
+ low: 1,
661
+ medium: 2,
662
+ high: 3,
663
+ critical: 4
664
+ };
665
+ function ruleType(rule) {
666
+ if (!isRecord(rule)) return void 0;
667
+ return stringValue(rule.type) ?? stringValue(rule.metric);
668
+ }
669
+ function ruleThreshold(rule) {
670
+ if (!isRecord(rule)) return void 0;
671
+ return numberValue(rule.threshold) ?? numberValue(rule.threshold_pct) ?? numberValue(rule.min_value) ?? numberValue(rule.value);
672
+ }
673
+ function collectNumbersByKey(value, keyPatterns) {
674
+ const found = [];
675
+ const visit = (item, key = "") => {
676
+ if (found.length >= 100) return;
677
+ if (typeof item === "number" && Number.isFinite(item)) {
678
+ if (keyPatterns.some((pattern) => pattern.test(key))) found.push(item);
679
+ return;
680
+ }
681
+ if (typeof item === "string" && item.trim()) {
682
+ const parsed = Number(item.replace(/[$,%]/g, ""));
683
+ if (Number.isFinite(parsed) && keyPatterns.some((pattern) => pattern.test(key))) {
684
+ found.push(parsed);
685
+ }
686
+ return;
687
+ }
688
+ if (Array.isArray(item)) {
689
+ item.forEach((child) => visit(child, key));
690
+ return;
691
+ }
692
+ if (isRecord(item)) {
693
+ Object.entries(item).forEach(
694
+ ([childKey, child]) => visit(child, childKey)
695
+ );
696
+ }
697
+ };
698
+ visit(value);
699
+ return found;
700
+ }
701
+ function maxAbsPercentChange(previous, current) {
702
+ const count = Math.min(previous.length, current.length);
703
+ let maxChange = null;
704
+ for (let index = 0; index < count; index++) {
705
+ const before = previous[index];
706
+ const after = current[index];
707
+ if (!Number.isFinite(before) || !Number.isFinite(after) || before === 0)
708
+ continue;
709
+ const change = Math.abs((after - before) / before * 100);
710
+ maxChange = maxChange === null ? change : Math.max(maxChange, change);
711
+ }
712
+ return maxChange;
713
+ }
714
+ function thresholdSeverity(value, threshold) {
715
+ if (value >= threshold * 5) return "critical";
716
+ if (value >= threshold * 3) return "high";
717
+ if (value >= threshold * 1.5) return "medium";
718
+ return "low";
719
+ }
720
+ function detectRiskSeverity(snapshot) {
721
+ const text = sortedJson(snapshot).toLowerCase();
722
+ if (/\bcritical\b|is_malicious[^a-z0-9]*true|honeypot[^a-z0-9]*true|rugpull[^a-z0-9]*true/.test(
723
+ text
724
+ )) {
725
+ return "critical";
726
+ }
727
+ if (/\bhigh\b|blacklist[^a-z0-9]*true|phishing[^a-z0-9]*true/.test(text)) {
728
+ return "high";
729
+ }
730
+ if (/\bmedium\b|warning|suspicious/.test(text)) return "medium";
731
+ if (/\blow\b/.test(text)) return "low";
732
+ return null;
733
+ }
734
+ function severityAtLeast(actual, minimum) {
735
+ return RULE_SEVERITY_ORDER[actual] >= RULE_SEVERITY_ORDER[minimum];
736
+ }
737
+ function evaluateMonitorRules(args) {
738
+ const evaluations = [];
739
+ for (const rule of args.monitor.rules) {
740
+ const type = ruleType(rule);
741
+ if (!type) continue;
742
+ if (type === "risk_severity_at_least") {
743
+ const minimum = isRecord(rule) && stringValue(rule.severity) || isRecord(rule) && stringValue(rule.minimum) || "high";
744
+ if (!["low", "medium", "high", "critical"].includes(minimum)) continue;
745
+ const actual = detectRiskSeverity(args.currentSnapshot);
746
+ if (actual && severityAtLeast(actual, minimum)) {
747
+ evaluations.push({
748
+ kind: "rule_risk_severity_at_least",
749
+ severity: actual,
750
+ title: `${args.monitor.name} matched risk severity rule`,
751
+ evidence: {
752
+ rule,
753
+ actual_severity: actual,
754
+ minimum_severity: minimum
755
+ }
756
+ });
757
+ }
758
+ continue;
759
+ }
760
+ const threshold = ruleThreshold(rule);
761
+ if (threshold === void 0 || threshold <= 0) continue;
762
+ const patternsByType = {
763
+ price_change_pct: [/price/i, /usd/i],
764
+ tvl_change_pct: [/tvl/i, /liquidity/i, /totalLiquidityUSD/i],
765
+ fee_change_pct: [/fee/i, /fees/i, /total24h/i, /total7d/i],
766
+ liquidity_change_pct: [
767
+ /liquidity/i,
768
+ /reserve.*usd/i,
769
+ /totalLiquidityUSD/i
770
+ ],
771
+ transfer_value_usd: [/value.*usd/i, /amount.*usd/i, /usd/i]
772
+ };
773
+ const patterns = patternsByType[type];
774
+ if (!patterns) continue;
775
+ const currentNumbers = collectNumbersByKey(args.currentSnapshot, patterns);
776
+ if (type === "transfer_value_usd") {
777
+ const maxValue = Math.max(...currentNumbers, 0);
778
+ if (maxValue >= threshold) {
779
+ evaluations.push({
780
+ kind: "rule_transfer_value_usd",
781
+ severity: thresholdSeverity(maxValue, threshold),
782
+ title: `${args.monitor.name} matched transfer value rule`,
783
+ evidence: { rule, max_value_usd: maxValue, threshold }
784
+ });
785
+ }
786
+ continue;
787
+ }
788
+ if (!args.previousSnapshot) continue;
789
+ const previousNumbers = collectNumbersByKey(
790
+ args.previousSnapshot,
791
+ patterns
792
+ );
793
+ const maxChange = maxAbsPercentChange(previousNumbers, currentNumbers);
794
+ if (maxChange !== null && maxChange >= threshold) {
795
+ evaluations.push({
796
+ kind: `rule_${type}`,
797
+ severity: thresholdSeverity(maxChange, threshold),
798
+ title: `${args.monitor.name} matched ${type} rule`,
799
+ evidence: { rule, max_change_pct: maxChange, threshold }
800
+ });
801
+ }
802
+ }
803
+ return evaluations;
804
+ }
805
+ async function executeMonitorWorkflow(monitor, executeTool, now) {
806
+ const definition = buildMonitorWorkflowDefinition(monitor);
807
+ const results = {};
808
+ const toolCalls = [];
809
+ const failedCalls = [];
810
+ for (const call of definition.calls) {
811
+ const result = await executeTool(call.toolName, call.args);
812
+ const record = {
813
+ tool: call.toolName,
814
+ args: call.args,
815
+ ok: result.ok,
816
+ duration_ms: result.durationMs,
817
+ runtime_status: result.meta.runtime_status,
818
+ ...result.error && { error: result.error.message }
819
+ };
820
+ toolCalls.push(record);
821
+ if (!result.ok) failedCalls.push(record);
822
+ results[call.snapshotKey] = result.ok ? compactForStorage(result.data) : { error: result.error?.message ?? "Tool call failed" };
823
+ }
824
+ const snapshot = {
825
+ type: definition.type,
826
+ monitor_id: monitor.id,
827
+ captured_at: now.toISOString(),
828
+ target: definition.target,
829
+ results
830
+ };
831
+ return {
832
+ type: definition.type,
833
+ label: definition.label,
834
+ snapshot,
835
+ fingerprint: fingerprintSnapshot(snapshot),
836
+ toolCalls,
837
+ failedCalls
838
+ };
839
+ }
840
+ var SupabaseMonitorRunnerStore = class {
841
+ supabase() {
842
+ if (!isSupabaseConfigured()) {
843
+ throw new Error(
844
+ "Supabase is not configured. Set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY."
845
+ );
846
+ }
847
+ return getSupabaseAdmin();
848
+ }
849
+ async claimDueMonitors(args) {
850
+ const { data, error } = await this.supabase().rpc("claim_due_monitors", {
851
+ batch_limit: args.batchSize,
852
+ worker_id: args.workerId,
853
+ lock_timeout_seconds: args.lockTimeoutSeconds
854
+ });
855
+ if (error) {
856
+ secureLogger.error("claim_due_monitors failed", error, {
857
+ workerId: args.workerId
858
+ });
859
+ return {
860
+ ok: false,
861
+ code: "STATE_BACKEND_UNAVAILABLE",
862
+ message: `Failed to claim due monitors: ${error.message}`
863
+ };
864
+ }
865
+ return { ok: true, data: data ?? [] };
866
+ }
867
+ async createRun(args) {
868
+ const { data, error } = await this.supabase().from("monitor_runs").insert({
869
+ monitor_id: args.monitor.id,
870
+ user_id: args.monitor.user_id,
871
+ subject_id: args.monitor.subject_id,
872
+ status: "running",
873
+ source: args.source,
874
+ metadata: { worker_id: args.workerId }
875
+ }).select("*").single();
876
+ if (error)
877
+ throw new Error(`Failed to create monitor run: ${error.message}`);
878
+ return data;
879
+ }
880
+ async getLatestSnapshot(monitorId) {
881
+ const { data, error } = await this.supabase().from("monitor_snapshots").select("*").eq("monitor_id", monitorId).order("created_at", { ascending: false }).limit(1).maybeSingle();
882
+ if (error)
883
+ throw new Error(`Failed to fetch latest snapshot: ${error.message}`);
884
+ return data ?? null;
885
+ }
886
+ async writeSnapshot(args) {
887
+ const { error } = await this.supabase().from("monitor_snapshots").insert({
888
+ monitor_id: args.monitor.id,
889
+ user_id: args.monitor.user_id,
890
+ subject_id: args.monitor.subject_id,
891
+ run_id: args.runId,
892
+ snapshot: args.snapshot,
893
+ fingerprint: args.fingerprint
894
+ });
895
+ if (error && error.code !== "23505") {
896
+ throw new Error(`Failed to write snapshot: ${error.message}`);
897
+ }
898
+ }
899
+ async writeObservation(args) {
900
+ const { error } = await this.supabase().from("observations").insert({
901
+ monitor_id: args.monitor.id,
902
+ user_id: args.monitor.user_id,
903
+ subject_id: args.monitor.subject_id,
904
+ run_id: args.runId,
905
+ kind: args.kind,
906
+ severity: args.severity,
907
+ title: args.title,
908
+ evidence: args.evidence
909
+ });
910
+ if (error) throw new Error(`Failed to write observation: ${error.message}`);
911
+ }
912
+ async writeAlert(args) {
913
+ const { error } = await this.supabase().from("alerts").insert({
914
+ monitor_id: args.monitor.id,
915
+ user_id: args.monitor.user_id,
916
+ subject_id: args.monitor.subject_id,
917
+ severity: args.severity,
918
+ title: args.title,
919
+ body: args.body,
920
+ payload: args.payload
921
+ });
922
+ if (error) throw new Error(`Failed to write alert: ${error.message}`);
923
+ }
924
+ async finishRun(args) {
925
+ const { error } = await this.supabase().from("monitor_runs").update({
926
+ status: args.status,
927
+ completed_at: (/* @__PURE__ */ new Date()).toISOString(),
928
+ tool_calls: args.toolCalls,
929
+ summary: args.summary,
930
+ error_code: args.errorCode ?? null,
931
+ error_message: args.errorMessage ?? null,
932
+ metadata: args.metadata ?? {}
933
+ }).eq("id", args.runId);
934
+ if (error)
935
+ throw new Error(`Failed to finish monitor run: ${error.message}`);
936
+ }
937
+ async releaseMonitor(args) {
938
+ const { error } = await this.supabase().from("monitors").update({
939
+ last_run_at: args.lastRunAt,
940
+ next_run_at: args.nextRunAt,
941
+ locked_at: null,
942
+ locked_by: null,
943
+ ...args.status && { status: args.status }
944
+ }).eq("id", args.monitor.id).eq("user_id", args.monitor.user_id).eq("subject_id", args.monitor.subject_id);
945
+ if (error) throw new Error(`Failed to release monitor: ${error.message}`);
946
+ }
947
+ };
948
+ async function processMonitor(monitor, options) {
949
+ const run = await options.store.createRun({
950
+ monitor,
951
+ workerId: options.workerId,
952
+ source: options.source
953
+ });
954
+ const completedAt = options.now.toISOString();
955
+ const nextRunAt = computeNextRunAt(monitor.cadence, options.now);
956
+ try {
957
+ const debit = await debitMonitorRunCredit({
958
+ monitor,
959
+ runId: run.id,
960
+ consumeCredit: options.consumeCredit
961
+ });
962
+ if (!debit.ok) {
963
+ await options.store.finishRun({
964
+ runId: run.id,
965
+ status: "skipped",
966
+ summary: debit.message,
967
+ toolCalls: [],
968
+ errorCode: debit.code,
969
+ errorMessage: debit.message
970
+ });
971
+ await options.store.releaseMonitor({
972
+ monitor,
973
+ lastRunAt: completedAt,
974
+ nextRunAt
975
+ });
976
+ return {
977
+ monitorId: monitor.id,
978
+ kind: monitor.kind,
979
+ status: "skipped",
980
+ runId: run.id,
981
+ summary: debit.message,
982
+ nextRunAt,
983
+ observations: 0,
984
+ alerts: 0
985
+ };
986
+ }
987
+ const previous = await options.store.getLatestSnapshot(monitor.id);
988
+ const workflow = await executeMonitorWorkflow(
989
+ monitor,
990
+ options.executeTool,
991
+ options.now
992
+ );
993
+ await options.store.writeSnapshot({
994
+ monitor,
995
+ runId: run.id,
996
+ snapshot: workflow.snapshot,
997
+ fingerprint: workflow.fingerprint
998
+ });
999
+ const ruleEvaluations = evaluateMonitorRules({
1000
+ monitor,
1001
+ previousSnapshot: previous?.snapshot ?? null,
1002
+ currentSnapshot: workflow.snapshot
1003
+ });
1004
+ let observations = 0;
1005
+ let alerts = 0;
1006
+ const observationPrefix = workflow.type;
1007
+ let summary = `${workflow.label} monitor completed with no material change.`;
1008
+ if (!previous) {
1009
+ summary = `${workflow.label} monitor baseline snapshot recorded.`;
1010
+ await options.store.writeObservation({
1011
+ monitor,
1012
+ runId: run.id,
1013
+ kind: `${observationPrefix}_baseline`,
1014
+ severity: "info",
1015
+ title: `Baseline recorded for ${monitor.name}`,
1016
+ evidence: { fingerprint: workflow.fingerprint }
1017
+ });
1018
+ observations++;
1019
+ } else if (previous.fingerprint !== workflow.fingerprint) {
1020
+ summary = `${workflow.label} monitor detected a snapshot change.`;
1021
+ const evidence = {
1022
+ previous_fingerprint: previous.fingerprint,
1023
+ current_fingerprint: workflow.fingerprint,
1024
+ failed_calls: workflow.failedCalls.map((call) => call.tool)
1025
+ };
1026
+ await options.store.writeObservation({
1027
+ monitor,
1028
+ runId: run.id,
1029
+ kind: `${observationPrefix}_change`,
1030
+ severity: workflow.failedCalls.length > 0 ? "medium" : "low",
1031
+ title: `${workflow.label} monitor changed: ${monitor.name}`,
1032
+ evidence
1033
+ });
1034
+ observations++;
1035
+ if (hasAlertRule(monitor)) {
1036
+ await options.store.writeAlert({
1037
+ monitor,
1038
+ severity: workflow.failedCalls.length > 0 ? "medium" : "low",
1039
+ title: `${workflow.label} monitor changed: ${monitor.name}`,
1040
+ body: `Hive detected a changed ${workflow.type} snapshot for this monitor.`,
1041
+ payload: evidence
1042
+ });
1043
+ alerts++;
1044
+ }
1045
+ }
1046
+ for (const evaluation of ruleEvaluations) {
1047
+ await options.store.writeObservation({
1048
+ monitor,
1049
+ runId: run.id,
1050
+ kind: evaluation.kind,
1051
+ severity: evaluation.severity,
1052
+ title: evaluation.title,
1053
+ evidence: evaluation.evidence
1054
+ });
1055
+ observations++;
1056
+ await options.store.writeAlert({
1057
+ monitor,
1058
+ severity: evaluation.severity,
1059
+ title: evaluation.title,
1060
+ body: "Hive matched a deterministic monitor rule threshold.",
1061
+ payload: evaluation.evidence
1062
+ });
1063
+ alerts++;
1064
+ }
1065
+ if (workflow.failedCalls.length > 0) {
1066
+ await options.store.writeObservation({
1067
+ monitor,
1068
+ runId: run.id,
1069
+ kind: `${observationPrefix}_monitor_degraded`,
1070
+ severity: "medium",
1071
+ title: `${workflow.label} monitor had ${workflow.failedCalls.length} failed tool call(s)`,
1072
+ evidence: {
1073
+ failed_calls: workflow.failedCalls.map((call) => ({
1074
+ tool: call.tool,
1075
+ error: call.error
1076
+ }))
1077
+ }
1078
+ });
1079
+ observations++;
1080
+ }
1081
+ await options.store.finishRun({
1082
+ runId: run.id,
1083
+ status: "completed",
1084
+ summary,
1085
+ toolCalls: workflow.toolCalls,
1086
+ metadata: {
1087
+ fingerprint: workflow.fingerprint,
1088
+ failed_call_count: workflow.failedCalls.length,
1089
+ rule_evaluation_count: ruleEvaluations.length
1090
+ }
1091
+ });
1092
+ await options.store.releaseMonitor({
1093
+ monitor,
1094
+ lastRunAt: completedAt,
1095
+ nextRunAt
1096
+ });
1097
+ return {
1098
+ monitorId: monitor.id,
1099
+ kind: monitor.kind,
1100
+ status: "completed",
1101
+ runId: run.id,
1102
+ summary,
1103
+ nextRunAt,
1104
+ observations,
1105
+ alerts
1106
+ };
1107
+ } catch (error) {
1108
+ const message = error instanceof Error ? error.message : String(error);
1109
+ await options.store.finishRun({
1110
+ runId: run.id,
1111
+ status: "failed",
1112
+ summary: "Monitor run failed.",
1113
+ toolCalls: [],
1114
+ errorCode: "MONITOR_RUN_FAILED",
1115
+ errorMessage: message
1116
+ });
1117
+ await options.store.releaseMonitor({
1118
+ monitor,
1119
+ lastRunAt: completedAt,
1120
+ nextRunAt
1121
+ });
1122
+ return {
1123
+ monitorId: monitor.id,
1124
+ kind: monitor.kind,
1125
+ status: "failed",
1126
+ runId: run.id,
1127
+ summary: message,
1128
+ nextRunAt,
1129
+ observations: 0,
1130
+ alerts: 0
1131
+ };
1132
+ }
1133
+ }
1134
+ async function runDueMonitors(options = {}) {
1135
+ const store = options.store ?? new SupabaseMonitorRunnerStore();
1136
+ const workerId = options.workerId ?? DEFAULT_WORKER_ID;
1137
+ const batchSize = Math.max(
1138
+ 1,
1139
+ Math.min(options.batchSize ?? DEFAULT_BATCH_SIZE, 100)
1140
+ );
1141
+ const lockTimeoutSeconds = options.lockTimeoutSeconds ?? DEFAULT_LOCK_TIMEOUT_SECONDS;
1142
+ const now = options.now ?? /* @__PURE__ */ new Date();
1143
+ const executeTool = options.executeTool ?? executeToolByName;
1144
+ const consumeCredit = options.consumeCredit ?? defaultConsumeCredit;
1145
+ const source = options.source ?? "scheduled";
1146
+ const claimed = await store.claimDueMonitors({
1147
+ batchSize,
1148
+ workerId,
1149
+ lockTimeoutSeconds
1150
+ });
1151
+ if (!claimed.ok) {
1152
+ return {
1153
+ ok: false,
1154
+ claimed: 0,
1155
+ completed: 0,
1156
+ failed: 0,
1157
+ skipped: 0,
1158
+ results: [],
1159
+ error: claimed.message
1160
+ };
1161
+ }
1162
+ const results = [];
1163
+ for (const monitor of claimed.data) {
1164
+ results.push(
1165
+ await processMonitor(monitor, {
1166
+ store,
1167
+ workerId,
1168
+ now,
1169
+ source,
1170
+ executeTool,
1171
+ consumeCredit
1172
+ })
1173
+ );
1174
+ }
1175
+ return {
1176
+ ok: results.every((result) => result.status !== "failed"),
1177
+ claimed: claimed.data.length,
1178
+ completed: results.filter((result) => result.status === "completed").length,
1179
+ failed: results.filter((result) => result.status === "failed").length,
1180
+ skipped: results.filter((result) => result.status === "skipped").length,
1181
+ results
1182
+ };
1183
+ }
1184
+
1185
+ // src/services/rwa-snapshot.ts
1186
+ var INSERT_CHUNK_SIZE = 500;
1187
+ var SNAPSHOT_MIN_GAP_MS = 55 * 60 * 1e3;
1188
+ function snapshotRowFor(row, capturedAt) {
1189
+ return {
1190
+ venue: row.venue,
1191
+ symbol: row.symbol,
1192
+ underlying: row.underlying,
1193
+ asset_class: row.asset_class,
1194
+ funding_rate: row.funding_rate,
1195
+ annualized: row.funding_annualized,
1196
+ mark_price: row.mark_price,
1197
+ oi: row.open_interest_usd,
1198
+ mechanism: row.funding_mechanism,
1199
+ flags: {
1200
+ market: row.market,
1201
+ funding_interval_hours: row.funding_interval_hours,
1202
+ borrow_apr_annual: row.borrow_apr_annual,
1203
+ is_delisted: row.is_delisted,
1204
+ is_price_suspect: row.is_price_suspect,
1205
+ price_suspect_basis: row.price_suspect_basis,
1206
+ price_deviation_pct: row.price_deviation_pct,
1207
+ volume_24h_usd: row.volume_24h_usd
1208
+ },
1209
+ captured_at: capturedAt
1210
+ };
1211
+ }
1212
+ async function persistRwaSnapshot() {
1213
+ if (!isSupabaseConfigured()) {
1214
+ return {
1215
+ ok: true,
1216
+ skipped: true,
1217
+ reason: "supabase_not_configured",
1218
+ capturedRows: 0,
1219
+ venueResults: [],
1220
+ writeErrors: [],
1221
+ capturedAt: null
1222
+ };
1223
+ }
1224
+ const { results, rows } = await collectRwaRows({
1225
+ hyperliquidApi: new HyperliquidAPI()
1226
+ });
1227
+ if (rows.length === 0) {
1228
+ return {
1229
+ ok: false,
1230
+ skipped: false,
1231
+ reason: "no_rows_from_any_venue",
1232
+ capturedRows: 0,
1233
+ venueResults: results,
1234
+ writeErrors: [],
1235
+ capturedAt: null
1236
+ };
1237
+ }
1238
+ const supabase = getSupabaseAdmin();
1239
+ const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
1240
+ const payload = rows.map((row) => snapshotRowFor(row, capturedAt));
1241
+ const writeErrors = [];
1242
+ let insertedCount = 0;
1243
+ for (let i = 0; i < payload.length; i += INSERT_CHUNK_SIZE) {
1244
+ const chunk = payload.slice(i, i + INSERT_CHUNK_SIZE);
1245
+ const { error } = await supabase.from("rwa_funding_snapshots").insert(chunk);
1246
+ if (error) {
1247
+ writeErrors.push(`insert chunk ${i}: ${error.message}`);
1248
+ } else {
1249
+ insertedCount += chunk.length;
1250
+ }
1251
+ }
1252
+ const ok = writeErrors.length === 0;
1253
+ if (!ok) {
1254
+ secureLogger.warn("RWA snapshot write errors", {
1255
+ errorCount: writeErrors.length
1256
+ });
1257
+ }
1258
+ return {
1259
+ ok,
1260
+ skipped: false,
1261
+ capturedRows: insertedCount,
1262
+ venueResults: results,
1263
+ writeErrors,
1264
+ capturedAt
1265
+ };
1266
+ }
1267
+ async function runScheduledRwaSnapshot() {
1268
+ try {
1269
+ if ((getEnvVar("HIVE_RWA_SNAPSHOT_ENABLED") ?? "true") === "false") {
1270
+ return {
1271
+ ok: true,
1272
+ skipped: true,
1273
+ reason: "disabled_by_env",
1274
+ capturedRows: 0,
1275
+ venueResults: [],
1276
+ writeErrors: [],
1277
+ capturedAt: null
1278
+ };
1279
+ }
1280
+ if (!isSupabaseConfigured()) {
1281
+ return {
1282
+ ok: true,
1283
+ skipped: true,
1284
+ reason: "supabase_not_configured",
1285
+ capturedRows: 0,
1286
+ venueResults: [],
1287
+ writeErrors: [],
1288
+ capturedAt: null
1289
+ };
1290
+ }
1291
+ const supabase = getSupabaseAdmin();
1292
+ const { data, error } = await supabase.from("rwa_funding_snapshots").select("captured_at").order("captured_at", { ascending: false }).limit(1);
1293
+ if (error) {
1294
+ return {
1295
+ ok: false,
1296
+ skipped: false,
1297
+ reason: `cadence check failed: ${error.message}`,
1298
+ capturedRows: 0,
1299
+ venueResults: [],
1300
+ writeErrors: [error.message],
1301
+ capturedAt: null
1302
+ };
1303
+ }
1304
+ const latest = data?.[0]?.captured_at ? Date.parse(String(data[0].captured_at)) : null;
1305
+ if (latest != null && Number.isFinite(latest) && Date.now() - latest < SNAPSHOT_MIN_GAP_MS) {
1306
+ return {
1307
+ ok: true,
1308
+ skipped: true,
1309
+ reason: "within_cadence_gap",
1310
+ capturedRows: 0,
1311
+ venueResults: [],
1312
+ writeErrors: [],
1313
+ capturedAt: null
1314
+ };
1315
+ }
1316
+ return await persistRwaSnapshot();
1317
+ } catch (error) {
1318
+ const message = error instanceof Error ? error.message : String(error);
1319
+ return {
1320
+ ok: false,
1321
+ skipped: false,
1322
+ reason: message,
1323
+ capturedRows: 0,
1324
+ venueResults: [],
1325
+ writeErrors: [message],
1326
+ capturedAt: null
1327
+ };
1328
+ }
1329
+ }
1330
+
1331
+ // src/monitor-worker.ts
1332
+ function intEnv(name, fallback) {
1333
+ const parsed = Number.parseInt(process.env[name] ?? "", 10);
1334
+ return Number.isFinite(parsed) ? parsed : fallback;
1335
+ }
1336
+ async function runMonitorWorkerFromEnv() {
1337
+ try {
1338
+ const result = await runDueMonitors({
1339
+ batchSize: intEnv("HIVE_MONITOR_WORKER_BATCH_SIZE", 10),
1340
+ workerId: process.env.HIVE_MONITOR_WORKER_ID,
1341
+ lockTimeoutSeconds: intEnv("HIVE_MONITOR_LOCK_TIMEOUT_SECONDS", 300)
1342
+ }).catch((error) => ({
1343
+ ok: false,
1344
+ claimed: 0,
1345
+ completed: 0,
1346
+ failed: 0,
1347
+ skipped: 0,
1348
+ results: [],
1349
+ error: error instanceof Error ? error.message : String(error)
1350
+ }));
1351
+ const scheduledHealthCanaries = await runScheduledHealthCanaries().catch(
1352
+ (error) => ({
1353
+ ok: false,
1354
+ skipped: false,
1355
+ persistent: false,
1356
+ duration: 0,
1357
+ canaries: {},
1358
+ errors: [error instanceof Error ? error.message : String(error)],
1359
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1360
+ })
1361
+ );
1362
+ const rwaSnapshot = await runScheduledRwaSnapshot();
1363
+ console.log(
1364
+ JSON.stringify(
1365
+ {
1366
+ service: "hive-monitor-worker",
1367
+ worker_id: process.env.HIVE_MONITOR_WORKER_ID ?? null,
1368
+ ...result,
1369
+ scheduled_health_canaries: scheduledHealthCanaries,
1370
+ rwa_snapshot: rwaSnapshot
1371
+ },
1372
+ null,
1373
+ 2
1374
+ )
1375
+ );
1376
+ if (!result.ok || !scheduledHealthCanaries.ok) process.exitCode = 1;
1377
+ } finally {
1378
+ await closeRedisClient();
1379
+ }
1380
+ }
1381
+ var invokedPath = process.argv[1] ? resolve(process.argv[1]) : "";
1382
+ if (invokedPath === fileURLToPath(import.meta.url)) {
1383
+ runMonitorWorkerFromEnv().catch((error) => {
1384
+ console.error(
1385
+ JSON.stringify({
1386
+ service: "hive-monitor-worker",
1387
+ ok: false,
1388
+ error: error instanceof Error ? error.message : String(error)
1389
+ })
1390
+ );
1391
+ process.exitCode = 1;
1392
+ });
1393
+ }
1394
+ export {
1395
+ runMonitorWorkerFromEnv
1396
+ };