auramaxx 0.0.1

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 (418) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +77 -0
  3. package/apps/desktop-electron/main.js +428 -0
  4. package/bin/auramaxx.js +1063 -0
  5. package/docs/ADAPTERS.md +466 -0
  6. package/docs/AGENT_SETUP.md +159 -0
  7. package/docs/API.md +127 -0
  8. package/docs/APPS.md +199 -0
  9. package/docs/ARCHITECTURE.md +235 -0
  10. package/docs/AUTH.md +318 -0
  11. package/docs/BEST-PRACTICES.md +82 -0
  12. package/docs/CLI.md +141 -0
  13. package/docs/DESKTOP_ELECTRON.md +26 -0
  14. package/docs/DEVELOPING-APPS.md +453 -0
  15. package/docs/MCP.md +122 -0
  16. package/docs/PACKAGING_POLICY.md +19 -0
  17. package/docs/PERMISSION.md +137 -0
  18. package/docs/PROTOCOL.md +142 -0
  19. package/docs/README.md +50 -0
  20. package/docs/SKILLS.md +132 -0
  21. package/docs/TROUBLESHOOTING.md +376 -0
  22. package/docs/WORKSPACE.md +673 -0
  23. package/docs/agent-auth.md +14 -0
  24. package/docs/api/authentication.md +79 -0
  25. package/docs/api/secrets/api-keys.md +28 -0
  26. package/docs/api/secrets/credentials.md +80 -0
  27. package/docs/api/secrets/sharing.md +48 -0
  28. package/docs/api/system.md +41 -0
  29. package/docs/api/wallets/apps-strategies.md +66 -0
  30. package/docs/api/wallets/core.md +46 -0
  31. package/docs/api/wallets/data-portfolio.md +42 -0
  32. package/docs/aura-file.md +48 -0
  33. package/docs/core-concepts/FEATURES.md +114 -0
  34. package/docs/credentials.md +120 -0
  35. package/docs/external/HOW_TO_AURAMAXX/GETTING_SECRETS.md +33 -0
  36. package/docs/external/HOW_TO_AURAMAXX/README.md +45 -0
  37. package/docs/external/getting-started.md +10 -0
  38. package/docs/external/overview.md +19 -0
  39. package/docs/external/persona-paths.md +7 -0
  40. package/docs/external/share-secret.md +76 -0
  41. package/docs/external/why-aura.md +7 -0
  42. package/docs/security.md +227 -0
  43. package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
  44. package/docs/wallet/AI.md +508 -0
  45. package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
  46. package/docs/wallet/README.md +47 -0
  47. package/docs/wallet/STRATEGY.md +89 -0
  48. package/next.config.ts +28 -0
  49. package/package.json +167 -0
  50. package/postcss.config.mjs +8 -0
  51. package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
  52. package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
  53. package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
  54. package/prisma/migrations/20260222090000_update_admin_ttl_default/migration.sql +10 -0
  55. package/prisma/migrations/migration_lock.toml +3 -0
  56. package/prisma/schema.prisma +447 -0
  57. package/public/logo.webp +0 -0
  58. package/scripts/add-app.js +245 -0
  59. package/server/abi/SwapHelper.json +438 -0
  60. package/server/cli/approval.ts +447 -0
  61. package/server/cli/commands/actions.ts +474 -0
  62. package/server/cli/commands/api.ts +220 -0
  63. package/server/cli/commands/apikey.ts +277 -0
  64. package/server/cli/commands/app.ts +204 -0
  65. package/server/cli/commands/auth.ts +464 -0
  66. package/server/cli/commands/cron.ts +24 -0
  67. package/server/cli/commands/diary.ts +274 -0
  68. package/server/cli/commands/doctor.ts +1247 -0
  69. package/server/cli/commands/env.ts +476 -0
  70. package/server/cli/commands/experimental.ts +69 -0
  71. package/server/cli/commands/init.ts +798 -0
  72. package/server/cli/commands/lock.ts +157 -0
  73. package/server/cli/commands/mcp.ts +285 -0
  74. package/server/cli/commands/quickhack.ts +86 -0
  75. package/server/cli/commands/release-check.ts +231 -0
  76. package/server/cli/commands/restore.ts +314 -0
  77. package/server/cli/commands/service.ts +320 -0
  78. package/server/cli/commands/shell-hook.ts +512 -0
  79. package/server/cli/commands/skill.ts +216 -0
  80. package/server/cli/commands/start.ts +139 -0
  81. package/server/cli/commands/status.ts +59 -0
  82. package/server/cli/commands/stop.ts +36 -0
  83. package/server/cli/commands/token.ts +180 -0
  84. package/server/cli/commands/unlock.ts +50 -0
  85. package/server/cli/commands/vault.ts +1323 -0
  86. package/server/cli/commands/wallet.ts +209 -0
  87. package/server/cli/index.ts +280 -0
  88. package/server/cli/lib/approval-poll.ts +94 -0
  89. package/server/cli/lib/aura-parser.ts +64 -0
  90. package/server/cli/lib/credential-create.ts +74 -0
  91. package/server/cli/lib/credential-resolve.ts +280 -0
  92. package/server/cli/lib/dotenv-migrate.ts +116 -0
  93. package/server/cli/lib/dotenv-parser.ts +146 -0
  94. package/server/cli/lib/escalation.ts +57 -0
  95. package/server/cli/lib/http.ts +91 -0
  96. package/server/cli/lib/init-steps.ts +76 -0
  97. package/server/cli/lib/local-agent-trust.ts +45 -0
  98. package/server/cli/lib/lock-unlock-helper.ts +71 -0
  99. package/server/cli/lib/process.ts +162 -0
  100. package/server/cli/lib/prompt.ts +294 -0
  101. package/server/cli/lib/theme.ts +240 -0
  102. package/server/cli/socket.ts +579 -0
  103. package/server/cli/transport-client.ts +50 -0
  104. package/server/cron/index.ts +137 -0
  105. package/server/cron/job.ts +31 -0
  106. package/server/cron/jobs/balance-sync.ts +436 -0
  107. package/server/cron/jobs/incoming-scan.ts +506 -0
  108. package/server/cron/jobs/native-price.ts +70 -0
  109. package/server/cron/jobs/orphan-cleanup.ts +40 -0
  110. package/server/cron/jobs/strategy-runner.ts +175 -0
  111. package/server/cron/scheduler.ts +125 -0
  112. package/server/index.ts +420 -0
  113. package/server/lib/adapters/factory.ts +119 -0
  114. package/server/lib/adapters/index.ts +19 -0
  115. package/server/lib/adapters/router.ts +297 -0
  116. package/server/lib/adapters/telegram.ts +645 -0
  117. package/server/lib/adapters/types.ts +89 -0
  118. package/server/lib/adapters/webhook.ts +95 -0
  119. package/server/lib/address.ts +49 -0
  120. package/server/lib/agent-auth/contracts.ts +1194 -0
  121. package/server/lib/agent-profiles.ts +419 -0
  122. package/server/lib/ai.ts +285 -0
  123. package/server/lib/api-registry/contracts.ts +86 -0
  124. package/server/lib/api-registry/validation.ts +172 -0
  125. package/server/lib/apikey-migration.ts +258 -0
  126. package/server/lib/app-installer.ts +505 -0
  127. package/server/lib/app-tokens.ts +247 -0
  128. package/server/lib/approval-link.ts +27 -0
  129. package/server/lib/auth.ts +314 -0
  130. package/server/lib/auto-execute.ts +160 -0
  131. package/server/lib/batch.ts +242 -0
  132. package/server/lib/cold.ts +1048 -0
  133. package/server/lib/config.ts +408 -0
  134. package/server/lib/credential-access-audit.ts +85 -0
  135. package/server/lib/credential-access-policy.ts +111 -0
  136. package/server/lib/credential-health.ts +343 -0
  137. package/server/lib/credential-import.ts +608 -0
  138. package/server/lib/credential-scope.ts +102 -0
  139. package/server/lib/credential-shares.ts +190 -0
  140. package/server/lib/credential-transport.ts +533 -0
  141. package/server/lib/credential-vault.ts +77 -0
  142. package/server/lib/credentials.ts +422 -0
  143. package/server/lib/crypto.ts +8 -0
  144. package/server/lib/db.ts +58 -0
  145. package/server/lib/defaults.ts +386 -0
  146. package/server/lib/dex/index.ts +80 -0
  147. package/server/lib/dex/relay.ts +235 -0
  148. package/server/lib/dex/types.ts +59 -0
  149. package/server/lib/dex/uniswap.ts +370 -0
  150. package/server/lib/diary.ts +34 -0
  151. package/server/lib/dont-ask-again-policy.ts +41 -0
  152. package/server/lib/e2e-agent/artifacts.ts +36 -0
  153. package/server/lib/e2e-agent/contracts.ts +112 -0
  154. package/server/lib/e2e-agent/validation.ts +135 -0
  155. package/server/lib/encrypt.ts +114 -0
  156. package/server/lib/error.ts +20 -0
  157. package/server/lib/events.ts +217 -0
  158. package/server/lib/feature-flags.ts +93 -0
  159. package/server/lib/hot.ts +357 -0
  160. package/server/lib/human-action-summary.ts +80 -0
  161. package/server/lib/key-fingerprint.ts +28 -0
  162. package/server/lib/logger.ts +340 -0
  163. package/server/lib/network.ts +137 -0
  164. package/server/lib/notifications.ts +230 -0
  165. package/server/lib/oauth2-refresh.ts +241 -0
  166. package/server/lib/oursecret.ts +71 -0
  167. package/server/lib/passkey-credential.ts +360 -0
  168. package/server/lib/passkey.ts +68 -0
  169. package/server/lib/permissions.ts +299 -0
  170. package/server/lib/pino.ts +24 -0
  171. package/server/lib/policy-preview.ts +138 -0
  172. package/server/lib/price.ts +338 -0
  173. package/server/lib/prices.ts +34 -0
  174. package/server/lib/project-scope.ts +297 -0
  175. package/server/lib/resolve-action.ts +328 -0
  176. package/server/lib/resolve.ts +36 -0
  177. package/server/lib/secret-gist-share.ts +296 -0
  178. package/server/lib/sessions.ts +634 -0
  179. package/server/lib/socket-path.ts +56 -0
  180. package/server/lib/solana/connection.ts +26 -0
  181. package/server/lib/solana/jupiter.ts +128 -0
  182. package/server/lib/solana/transfer.ts +108 -0
  183. package/server/lib/solana/wallet.ts +136 -0
  184. package/server/lib/strategy/emits.ts +21 -0
  185. package/server/lib/strategy/engine.ts +1305 -0
  186. package/server/lib/strategy/executor.ts +115 -0
  187. package/server/lib/strategy/hook-context.ts +159 -0
  188. package/server/lib/strategy/hooks.ts +990 -0
  189. package/server/lib/strategy/index.ts +28 -0
  190. package/server/lib/strategy/installer.ts +305 -0
  191. package/server/lib/strategy/loader.ts +256 -0
  192. package/server/lib/strategy/message.ts +237 -0
  193. package/server/lib/strategy/repository.ts +218 -0
  194. package/server/lib/strategy/session-logger.ts +693 -0
  195. package/server/lib/strategy/sources.ts +288 -0
  196. package/server/lib/strategy/state.ts +189 -0
  197. package/server/lib/strategy/templates.ts +403 -0
  198. package/server/lib/strategy/tick.ts +404 -0
  199. package/server/lib/strategy/types.ts +230 -0
  200. package/server/lib/swap.ts +3 -0
  201. package/server/lib/temp.ts +86 -0
  202. package/server/lib/token-metadata.ts +86 -0
  203. package/server/lib/token-safety.ts +200 -0
  204. package/server/lib/token-search.ts +444 -0
  205. package/server/lib/totp.ts +194 -0
  206. package/server/lib/transactions.ts +123 -0
  207. package/server/lib/transport.ts +84 -0
  208. package/server/lib/txhistory/decoder.ts +262 -0
  209. package/server/lib/txhistory/enricher.ts +652 -0
  210. package/server/lib/txhistory/index.ts +391 -0
  211. package/server/lib/txhistory/signatures.ts +59 -0
  212. package/server/lib/update-check.ts +35 -0
  213. package/server/lib/verified-summary.ts +414 -0
  214. package/server/lib/view-registry.ts +80 -0
  215. package/server/mcp/profile-policy.ts +30 -0
  216. package/server/mcp/server.ts +1589 -0
  217. package/server/mcp/tools.ts +276 -0
  218. package/server/middleware/auth.ts +119 -0
  219. package/server/middleware/requestLogger.ts +84 -0
  220. package/server/routes/actions.ts +539 -0
  221. package/server/routes/adapters.ts +711 -0
  222. package/server/routes/addressbook.ts +113 -0
  223. package/server/routes/ai.ts +34 -0
  224. package/server/routes/apikeys.ts +343 -0
  225. package/server/routes/apps.ts +601 -0
  226. package/server/routes/auth.ts +406 -0
  227. package/server/routes/backup.ts +404 -0
  228. package/server/routes/batch.ts +270 -0
  229. package/server/routes/bookmarks.ts +162 -0
  230. package/server/routes/credential-shares.ts +380 -0
  231. package/server/routes/credential-vaults.ts +159 -0
  232. package/server/routes/credentials.ts +1782 -0
  233. package/server/routes/dashboard.ts +97 -0
  234. package/server/routes/defaults.ts +124 -0
  235. package/server/routes/flags.ts +11 -0
  236. package/server/routes/fund.ts +225 -0
  237. package/server/routes/heartbeat.ts +375 -0
  238. package/server/routes/import.ts +364 -0
  239. package/server/routes/launch.ts +665 -0
  240. package/server/routes/lock.ts +54 -0
  241. package/server/routes/logs.ts +68 -0
  242. package/server/routes/nuke.ts +111 -0
  243. package/server/routes/passkey-credentials.ts +99 -0
  244. package/server/routes/passkey.ts +366 -0
  245. package/server/routes/portfolio.ts +217 -0
  246. package/server/routes/price.ts +63 -0
  247. package/server/routes/resolve.ts +31 -0
  248. package/server/routes/security.ts +45 -0
  249. package/server/routes/send-evm.ts +241 -0
  250. package/server/routes/send-solana.ts +281 -0
  251. package/server/routes/send.ts +178 -0
  252. package/server/routes/setup.ts +210 -0
  253. package/server/routes/strategy.ts +894 -0
  254. package/server/routes/swap-evm.ts +352 -0
  255. package/server/routes/swap-solana.ts +176 -0
  256. package/server/routes/swap.ts +356 -0
  257. package/server/routes/token.ts +247 -0
  258. package/server/routes/unlock.ts +467 -0
  259. package/server/routes/views.ts +41 -0
  260. package/server/routes/wallet-assets.ts +361 -0
  261. package/server/routes/wallet-transactions.ts +515 -0
  262. package/server/routes/wallet.ts +709 -0
  263. package/server/types.ts +146 -0
  264. package/shared/credential-field-schema.ts +248 -0
  265. package/skills/auramaxx/HEARTBEAT.md +78 -0
  266. package/skills/auramaxx/SKILL.md +745 -0
  267. package/skills/auramaxx/docs/AGENT_SETUP.md +155 -0
  268. package/skills/auramaxx/docs/API.md +127 -0
  269. package/skills/auramaxx/docs/AUTH.md +318 -0
  270. package/skills/auramaxx/docs/CLI.md +130 -0
  271. package/skills/auramaxx/docs/MCP.md +122 -0
  272. package/skills/auramaxx/docs/TROUBLESHOOTING.md +357 -0
  273. package/skills/auramaxx/docs/WORKSPACE.md +673 -0
  274. package/skills/auramaxx/docs/security.md +227 -0
  275. package/skills/task-lifecycle/SKILL.md +378 -0
  276. package/src/app/api/[...doc]/page.tsx +36 -0
  277. package/src/app/api/agent-requests/route.ts +30 -0
  278. package/src/app/api/apps/install/route.ts +132 -0
  279. package/src/app/api/apps/manifests/route.ts +16 -0
  280. package/src/app/api/apps/static/[...path]/route.ts +57 -0
  281. package/src/app/api/docs/plain/route.ts +74 -0
  282. package/src/app/api/events/route.ts +92 -0
  283. package/src/app/api/page.tsx +290 -0
  284. package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
  285. package/src/app/api/workspace/[id]/apps/route.ts +81 -0
  286. package/src/app/api/workspace/[id]/export/route.ts +67 -0
  287. package/src/app/api/workspace/[id]/route.ts +168 -0
  288. package/src/app/api/workspace/auth.ts +40 -0
  289. package/src/app/api/workspace/config/route.ts +121 -0
  290. package/src/app/api/workspace/import/route.ts +127 -0
  291. package/src/app/api/workspace/route.ts +116 -0
  292. package/src/app/app-legacy-do-not-use/page.tsx +2245 -0
  293. package/src/app/apple-icon.png +0 -0
  294. package/src/app/approve/[actionId]/page.tsx +409 -0
  295. package/src/app/docs/DocsPageContent.tsx +269 -0
  296. package/src/app/docs/[...doc]/page.tsx +41 -0
  297. package/src/app/docs/page.tsx +38 -0
  298. package/src/app/favicon.ico +0 -0
  299. package/src/app/globals.css +819 -0
  300. package/src/app/health/page.tsx +5 -0
  301. package/src/app/hello/page.tsx +102 -0
  302. package/src/app/icon.png +0 -0
  303. package/src/app/layout.tsx +39 -0
  304. package/src/app/page.tsx +1964 -0
  305. package/src/app/privacy/page.tsx +63 -0
  306. package/src/app/providers.tsx +87 -0
  307. package/src/app/share/[token]/page.tsx +295 -0
  308. package/src/app/terms/page.tsx +80 -0
  309. package/src/components/ChainSelector.tsx +44 -0
  310. package/src/components/HumanActionBar.tsx +697 -0
  311. package/src/components/NotificationDrawer.tsx +387 -0
  312. package/src/components/PasskeyEnrollmentPrompt.tsx +235 -0
  313. package/src/components/apps/AgentKeysApp.tsx +490 -0
  314. package/src/components/apps/App.tsx +153 -0
  315. package/src/components/apps/AppGrid.tsx +15 -0
  316. package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
  317. package/src/components/apps/DraggableApp.tsx +562 -0
  318. package/src/components/apps/IFrameApp.tsx +73 -0
  319. package/src/components/apps/LogsApp.tsx +360 -0
  320. package/src/components/apps/SendApp.tsx +394 -0
  321. package/src/components/apps/SetupWizardApp.tsx +1004 -0
  322. package/src/components/apps/SystemDefaultsApp.tsx +845 -0
  323. package/src/components/apps/ThirdPartyApp.tsx +428 -0
  324. package/src/components/apps/TokenApp.tsx +319 -0
  325. package/src/components/apps/TransactionsApp.tsx +438 -0
  326. package/src/components/apps/WalletDetailApp.tsx +1505 -0
  327. package/src/components/apps/index.ts +13 -0
  328. package/src/components/design-system/Button.tsx +88 -0
  329. package/src/components/design-system/ChainIndicator.tsx +65 -0
  330. package/src/components/design-system/ChainSelector.tsx +147 -0
  331. package/src/components/design-system/ConfirmationModal.tsx +107 -0
  332. package/src/components/design-system/ConfirmationPopover.tsx +81 -0
  333. package/src/components/design-system/DownloadButton.tsx +149 -0
  334. package/src/components/design-system/Drawer.tsx +133 -0
  335. package/src/components/design-system/FilterDropdown.tsx +183 -0
  336. package/src/components/design-system/ItemPicker.tsx +157 -0
  337. package/src/components/design-system/Modal.tsx +296 -0
  338. package/src/components/design-system/Popover.tsx +142 -0
  339. package/src/components/design-system/TextInput.tsx +85 -0
  340. package/src/components/design-system/Toggle.tsx +65 -0
  341. package/src/components/design-system/TyvekCollapsibleSection.tsx +55 -0
  342. package/src/components/design-system/index.ts +14 -0
  343. package/src/components/docs/ClientSideMarkdown.tsx +51 -0
  344. package/src/components/docs/DocsSearchBar.tsx +118 -0
  345. package/src/components/docs/DocsThemeToggle.tsx +38 -0
  346. package/src/components/docs/PersistentDocGroup.tsx +91 -0
  347. package/src/components/docs/ShareUrlButton.tsx +33 -0
  348. package/src/components/docs/SidebarScrollMemory.tsx +56 -0
  349. package/src/components/health/CredentialHealthDashboard.tsx +214 -0
  350. package/src/components/icons/ChainIcons.tsx +72 -0
  351. package/src/components/layout/AppStoreDrawer.tsx +369 -0
  352. package/src/components/layout/ContentArea.tsx +21 -0
  353. package/src/components/layout/CreateViewModal.tsx +88 -0
  354. package/src/components/layout/LeftRail.tsx +114 -0
  355. package/src/components/layout/TabBar.tsx +284 -0
  356. package/src/components/layout/WalletSidebar.tsx +1030 -0
  357. package/src/components/layout/index.ts +6 -0
  358. package/src/components/marketing/AuraMaxxSpecOverlay.tsx +653 -0
  359. package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
  360. package/src/components/vault/ApiKeysConsole.tsx +1272 -0
  361. package/src/components/vault/AuditConsole.tsx +600 -0
  362. package/src/components/vault/CredentialDetail.tsx +625 -0
  363. package/src/components/vault/CredentialEmpty.tsx +55 -0
  364. package/src/components/vault/CredentialField.tsx +583 -0
  365. package/src/components/vault/CredentialForm.tsx +1484 -0
  366. package/src/components/vault/CredentialList.tsx +265 -0
  367. package/src/components/vault/CredentialRow.tsx +130 -0
  368. package/src/components/vault/CredentialShareModal.tsx +273 -0
  369. package/src/components/vault/CredentialVault.tsx +1662 -0
  370. package/src/components/vault/CredentialWalletWidget.tsx +103 -0
  371. package/src/components/vault/DocsConsole.tsx +113 -0
  372. package/src/components/vault/ImportCredentialsModal.tsx +578 -0
  373. package/src/components/vault/LargeTypeModal.tsx +88 -0
  374. package/src/components/vault/PasswordGenerator.tsx +232 -0
  375. package/src/components/vault/TOTPDisplay.tsx +108 -0
  376. package/src/components/vault/TotpSetupPanel.tsx +198 -0
  377. package/src/components/vault/VaultSidebar.tsx +881 -0
  378. package/src/components/vault/credentialFormName.ts +91 -0
  379. package/src/components/vault/hooks/useVaultKeyboardShortcuts.ts +69 -0
  380. package/src/components/vault/types.ts +56 -0
  381. package/src/context/AuthContext.tsx +365 -0
  382. package/src/context/PriceContext.tsx +113 -0
  383. package/src/context/ThemeContext.tsx +164 -0
  384. package/src/context/WebSocketContext.tsx +269 -0
  385. package/src/context/WorkspaceContext.tsx +668 -0
  386. package/src/hooks/index.ts +4 -0
  387. package/src/hooks/useAgentActions.ts +552 -0
  388. package/src/hooks/useBalance.ts +103 -0
  389. package/src/hooks/useBalances.ts +129 -0
  390. package/src/hooks/useTheme.ts +156 -0
  391. package/src/instrumentation.ts +12 -0
  392. package/src/lib/api-docs.ts +154 -0
  393. package/src/lib/api.ts +474 -0
  394. package/src/lib/app-loader.ts +148 -0
  395. package/src/lib/app-registry.ts +178 -0
  396. package/src/lib/app-sdk.ts +157 -0
  397. package/src/lib/audit-console-adapter.ts +151 -0
  398. package/src/lib/auth-client.ts +75 -0
  399. package/src/lib/config.ts +74 -0
  400. package/src/lib/credential-field-schema.ts +11 -0
  401. package/src/lib/crypto.ts +112 -0
  402. package/src/lib/db.ts +21 -0
  403. package/src/lib/docs.ts +544 -0
  404. package/src/lib/events.ts +363 -0
  405. package/src/lib/pino.ts +24 -0
  406. package/src/lib/theme-handlers.ts +168 -0
  407. package/src/lib/theme.ts +351 -0
  408. package/src/lib/tokenData.ts +378 -0
  409. package/src/lib/totp-import.ts +57 -0
  410. package/src/lib/vault-crypto.ts +129 -0
  411. package/src/lib/view-registry.ts +57 -0
  412. package/src/lib/websocket-server.ts +302 -0
  413. package/src/lib/websocket-setup.ts +79 -0
  414. package/src/lib/wordlist.ts +2050 -0
  415. package/src/lib/workspace-handlers.ts +285 -0
  416. package/start.sh +170 -0
  417. package/tailwind.config.ts +99 -0
  418. package/tsconfig.json +42 -0
@@ -0,0 +1,506 @@
1
+ /**
2
+ * Incoming Asset Discovery Job
3
+ * ============================
4
+ * Scans on-chain Transfer events for incoming tokens to HotWallets.
5
+ * New tokens pass a 3-gate spam filter (value, liquidity, safety)
6
+ * before being auto-tracked. Already-tracked tokens just get a
7
+ * Transaction record.
8
+ */
9
+
10
+ import { Connection, PublicKey } from '@solana/web3.js';
11
+ import type { CronJob, CronContext } from '../job';
12
+ import { getRpcUrl } from '../../lib/config';
13
+ import { isSolanaChain } from '../../lib/address';
14
+ import { EVENT_SIGNATURES } from '../../lib/txhistory/signatures';
15
+ import { decodeLogs, type RawLog } from '../../lib/txhistory/decoder';
16
+ import { resolveTokenMetadataBatch } from '../../lib/txhistory/enricher';
17
+ import { getTokenPrice } from '../../lib/price';
18
+ import { searchTokens } from '../../lib/token-search';
19
+ import { getTokenSafety } from '../../lib/token-safety';
20
+ import { upsertTokenMetadata } from '../../lib/token-metadata';
21
+ import { getErrorMessage } from '../../lib/error';
22
+
23
+ // ─── Helpers ──────────────────────────────────────────────────────────
24
+
25
+ /** Pad a 20-byte address to 32-byte topic for eth_getLogs topic filter */
26
+ function padAddress(address: string): string {
27
+ return '0x' + address.slice(2).toLowerCase().padStart(64, '0');
28
+ }
29
+
30
+ /** Format raw bigint token amount with decimals */
31
+ function formatAmount(raw: bigint, decimals: number): string {
32
+ if (raw === 0n) return '0';
33
+ const divisor = 10n ** BigInt(decimals);
34
+ const whole = raw / divisor;
35
+ const fraction = raw % divisor;
36
+ if (fraction === 0n) return whole.toString();
37
+ const fractionStr = fraction.toString().padStart(decimals, '0').replace(/0+$/, '');
38
+ return `${whole}.${fractionStr}`;
39
+ }
40
+
41
+ // ─── Token Processing Pipeline ────────────────────────────────────────
42
+
43
+ interface DiscoveredTransfer {
44
+ tokenAddress: string;
45
+ walletAddress: string;
46
+ amount: bigint;
47
+ from: string;
48
+ txHash: string;
49
+ blockNumber: bigint;
50
+ }
51
+
52
+ /**
53
+ * Process a discovered incoming transfer.
54
+ * If the token is already tracked, just write a Transaction record.
55
+ * If new, run the 3-gate spam filter before auto-tracking.
56
+ */
57
+ async function processDiscoveredToken(
58
+ transfer: DiscoveredTransfer,
59
+ chain: string,
60
+ ctx: CronContext,
61
+ ): Promise<void> {
62
+ const { tokenAddress, walletAddress, amount, from, txHash, blockNumber } = transfer;
63
+
64
+ // Check for existing Transaction with this txHash+chain (dedup)
65
+ const existingTx = await ctx.prisma.transaction.findUnique({
66
+ where: { txHash_chain: { txHash, chain } },
67
+ });
68
+ if (existingTx) return;
69
+
70
+ // Check if already tracked
71
+ const existing = await ctx.prisma.trackedAsset.findUnique({
72
+ where: {
73
+ walletAddress_tokenAddress_chain: { walletAddress, tokenAddress, chain },
74
+ },
75
+ });
76
+
77
+ // Resolve decimals for formatting
78
+ const tokenMap = await resolveTokenMetadataBatch([tokenAddress], chain);
79
+ const tokenInfo = tokenMap.get(tokenAddress);
80
+ const decimals = tokenInfo?.decimals ?? 18;
81
+ const symbol = tokenInfo?.symbol ?? 'UNKNOWN';
82
+ const formattedAmount = formatAmount(amount, decimals);
83
+
84
+ if (existing) {
85
+ // Already tracked — just write the Transaction record
86
+ await ctx.prisma.transaction.create({
87
+ data: {
88
+ walletAddress,
89
+ txHash,
90
+ type: 'receive',
91
+ status: 'confirmed',
92
+ tokenAddress,
93
+ tokenAmount: formattedAmount,
94
+ from,
95
+ to: walletAddress,
96
+ blockNumber: blockNumber <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(blockNumber) : null,
97
+ chain,
98
+ },
99
+ }).catch(() => {}); // unique constraint race
100
+ return;
101
+ }
102
+
103
+ // New token — run spam filter gates
104
+
105
+ // Gate 1: Value
106
+ const minValue = ctx.defaults.get<number>('discovery.min_value_usd', 0.5);
107
+ const priceResult = await getTokenPrice(tokenAddress, chain);
108
+ if (priceResult) {
109
+ const valueUsd = parseFloat(formattedAmount) * parseFloat(priceResult.priceUsd);
110
+ if (valueUsd < minValue) {
111
+ ctx.log.debug({ tokenAddress, chain, valueUsd, minValue }, 'Discovery: below value gate');
112
+ return;
113
+ }
114
+ } else {
115
+ // No price data — skip (can't verify value)
116
+ ctx.log.debug({ tokenAddress, chain }, 'Discovery: no price data, skipping');
117
+ return;
118
+ }
119
+
120
+ // Gate 2: Liquidity
121
+ const minLiquidity = ctx.defaults.get<number>('discovery.min_liquidity_usd', 1000);
122
+ const searchResults = await searchTokens(tokenAddress, { chain });
123
+ const bestResult = searchResults[0];
124
+ if (!bestResult || bestResult.liquidity < minLiquidity) {
125
+ ctx.log.debug({
126
+ tokenAddress,
127
+ chain,
128
+ liquidity: bestResult?.liquidity ?? 0,
129
+ minLiquidity,
130
+ }, 'Discovery: below liquidity gate');
131
+ return;
132
+ }
133
+
134
+ // Gate 3: Safety
135
+ const safetyEnabled = ctx.defaults.get<boolean>('discovery.safety_enabled', true);
136
+ if (safetyEnabled) {
137
+ const safety = await getTokenSafety(tokenAddress, chain);
138
+ if (safety) {
139
+ if (safety.isHoneypot || parseFloat(safety.buyTax) > 50 || parseFloat(safety.sellTax) > 50) {
140
+ ctx.log.debug({ tokenAddress, chain, isHoneypot: safety.isHoneypot, buyTax: safety.buyTax, sellTax: safety.sellTax }, 'Discovery: failed safety gate');
141
+ // Still write metadata for manual lookup
142
+ upsertTokenMetadata(tokenAddress, chain, {
143
+ symbol: tokenInfo?.symbol,
144
+ name: tokenInfo?.name,
145
+ decimals,
146
+ });
147
+ return;
148
+ }
149
+ }
150
+ // safety === null → GoPlus doesn't have data, let it through
151
+ }
152
+
153
+ // All gates passed — auto-track
154
+ ctx.log.info({ tokenAddress, chain, symbol, walletAddress }, 'Discovery: auto-tracking new token');
155
+
156
+ upsertTokenMetadata(tokenAddress, chain, {
157
+ symbol: tokenInfo?.symbol,
158
+ name: tokenInfo?.name,
159
+ decimals,
160
+ icon: tokenInfo?.icon,
161
+ });
162
+
163
+ await ctx.prisma.trackedAsset.upsert({
164
+ where: {
165
+ walletAddress_tokenAddress_chain: { walletAddress, tokenAddress, chain },
166
+ },
167
+ create: {
168
+ walletAddress,
169
+ tokenAddress,
170
+ chain,
171
+ symbol: tokenInfo?.symbol,
172
+ name: tokenInfo?.name,
173
+ decimals,
174
+ icon: tokenInfo?.icon ?? bestResult?.imageUrl,
175
+ },
176
+ update: {},
177
+ });
178
+
179
+ await ctx.prisma.transaction.create({
180
+ data: {
181
+ walletAddress,
182
+ txHash,
183
+ type: 'receive',
184
+ status: 'confirmed',
185
+ tokenAddress,
186
+ tokenAmount: formattedAmount,
187
+ from,
188
+ to: walletAddress,
189
+ blockNumber: blockNumber <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(blockNumber) : null,
190
+ chain,
191
+ },
192
+ }).catch(() => {}); // unique constraint race
193
+
194
+ await ctx.emit('asset:discovered', {
195
+ walletAddress,
196
+ tokenAddress,
197
+ chain,
198
+ symbol,
199
+ amount: formattedAmount,
200
+ });
201
+ }
202
+
203
+ // ─── EVM Scanning ─────────────────────────────────────────────────────
204
+
205
+ async function scanEvmChain(chain: string, ctx: CronContext): Promise<void> {
206
+ const rpcUrl = await getRpcUrl(chain);
207
+ const syncKey = `${chain}:discovery`;
208
+
209
+ // Get all hot wallet addresses for this chain
210
+ const hotWallets = await ctx.prisma.hotWallet.findMany({
211
+ where: { chain, hidden: false },
212
+ select: { address: true },
213
+ });
214
+ if (hotWallets.length === 0) return;
215
+
216
+ const walletAddresses = hotWallets.map((w) => w.address.toLowerCase());
217
+ const walletSet = new Set(walletAddresses);
218
+
219
+ // Read block cursor from SyncState
220
+ const syncState = await ctx.prisma.syncState.findUnique({ where: { chain: syncKey } });
221
+ const maxBlocksPerTick = ctx.defaults.get<number>('discovery.max_blocks_per_tick', 2000);
222
+ const maxInitialLookback = ctx.defaults.get<number>('discovery.max_initial_lookback', 302400);
223
+
224
+ // Get current block number
225
+ const blockRes = await fetch(rpcUrl, {
226
+ method: 'POST',
227
+ headers: { 'Content-Type': 'application/json' },
228
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_blockNumber', params: [] }),
229
+ signal: AbortSignal.timeout(10000),
230
+ });
231
+ const blockData = await blockRes.json();
232
+ const latestBlock = BigInt(blockData.result);
233
+
234
+ // Calculate scan range
235
+ let fromBlock: bigint;
236
+ if (syncState?.lastBlock) {
237
+ fromBlock = BigInt(syncState.lastBlock) + 1n;
238
+ } else {
239
+ fromBlock = latestBlock - BigInt(maxInitialLookback);
240
+ if (fromBlock < 0n) fromBlock = 0n;
241
+ }
242
+
243
+ const toBlock = fromBlock + BigInt(maxBlocksPerTick) - 1n < latestBlock
244
+ ? fromBlock + BigInt(maxBlocksPerTick) - 1n
245
+ : latestBlock;
246
+
247
+ if (fromBlock > toBlock) return; // already caught up
248
+
249
+ // Build topic2 filter — pad wallet addresses for topic2 (receiver)
250
+ const paddedAddresses = walletAddresses.map(padAddress);
251
+
252
+ // eth_getLogs: Transfer events TO our wallets
253
+ const logsRes = await fetch(rpcUrl, {
254
+ method: 'POST',
255
+ headers: { 'Content-Type': 'application/json' },
256
+ body: JSON.stringify({
257
+ jsonrpc: '2.0',
258
+ id: 2,
259
+ method: 'eth_getLogs',
260
+ params: [{
261
+ fromBlock: '0x' + fromBlock.toString(16),
262
+ toBlock: '0x' + toBlock.toString(16),
263
+ topics: [
264
+ EVENT_SIGNATURES.TRANSFER,
265
+ null,
266
+ paddedAddresses.length === 1 ? paddedAddresses[0] : paddedAddresses,
267
+ ],
268
+ }],
269
+ }),
270
+ signal: AbortSignal.timeout(30000),
271
+ });
272
+ const logsData = await logsRes.json();
273
+
274
+ if (logsData.error) {
275
+ ctx.log.warn({ chain, error: logsData.error }, 'Discovery: eth_getLogs RPC error');
276
+ // Update error state but don't advance cursor
277
+ await ctx.prisma.syncState.upsert({
278
+ where: { chain: syncKey },
279
+ create: { chain: syncKey, lastSyncStatus: 'error', lastError: logsData.error.message, syncCount: 1 },
280
+ update: { lastSyncStatus: 'error', lastError: logsData.error.message, syncCount: { increment: 1 } },
281
+ });
282
+ return;
283
+ }
284
+
285
+ const rawLogs: RawLog[] = (logsData.result || []).map((log: any) => ({
286
+ address: log.address,
287
+ topics: log.topics,
288
+ data: log.data,
289
+ transactionHash: log.transactionHash,
290
+ logIndex: parseInt(log.logIndex, 16),
291
+ blockNumber: BigInt(log.blockNumber),
292
+ }));
293
+
294
+ // Decode logs
295
+ const decoded = decodeLogs(rawLogs);
296
+
297
+ // Filter to ERC-20 transfers where `to` is one of our wallets
298
+ const incomingTransfers: DiscoveredTransfer[] = [];
299
+ for (const event of decoded) {
300
+ if (event.type !== 'transfer') continue;
301
+ const to = (event.params.to as string).toLowerCase();
302
+ if (!walletSet.has(to)) continue;
303
+
304
+ incomingTransfers.push({
305
+ tokenAddress: event.contractAddress,
306
+ walletAddress: to,
307
+ amount: event.params.amount as bigint,
308
+ from: (event.params.from as string).toLowerCase(),
309
+ txHash: event.txHash,
310
+ blockNumber: event.blockNumber,
311
+ });
312
+ }
313
+
314
+ // Process each transfer
315
+ for (const transfer of incomingTransfers) {
316
+ try {
317
+ await processDiscoveredToken(transfer, chain, ctx);
318
+ } catch (err) {
319
+ ctx.log.debug({ err, txHash: transfer.txHash, token: transfer.tokenAddress }, 'Discovery: failed to process transfer');
320
+ }
321
+ }
322
+
323
+ // Advance cursor
324
+ await ctx.prisma.syncState.upsert({
325
+ where: { chain: syncKey },
326
+ create: {
327
+ chain: syncKey,
328
+ lastSyncAt: new Date(),
329
+ lastSyncStatus: 'ok',
330
+ lastBlock: toBlock.toString(),
331
+ syncCount: 1,
332
+ },
333
+ update: {
334
+ lastSyncAt: new Date(),
335
+ lastSyncStatus: 'ok',
336
+ lastError: null,
337
+ lastBlock: toBlock.toString(),
338
+ syncCount: { increment: 1 },
339
+ },
340
+ });
341
+ }
342
+
343
+ // ─── Solana Scanning ──────────────────────────────────────────────────
344
+
345
+ async function scanSolanaChain(chain: string, ctx: CronContext): Promise<void> {
346
+ const rpcUrl = await getRpcUrl(chain);
347
+ const connection = new Connection(rpcUrl, 'confirmed');
348
+ const syncKey = `${chain}:discovery`;
349
+
350
+ const hotWallets = await ctx.prisma.hotWallet.findMany({
351
+ where: { chain, hidden: false },
352
+ select: { address: true },
353
+ });
354
+ if (hotWallets.length === 0) return;
355
+
356
+ const syncState = await ctx.prisma.syncState.findUnique({ where: { chain: syncKey } });
357
+ const lastSyncAt = syncState?.lastSyncAt ?? new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
358
+
359
+ for (const wallet of hotWallets) {
360
+ try {
361
+ const pubkey = new PublicKey(wallet.address);
362
+ const signatures = await connection.getSignaturesForAddress(pubkey, { limit: 50 });
363
+
364
+ // Filter to signatures newer than last scan
365
+ const newSigs = signatures.filter(
366
+ (s) => s.blockTime && s.blockTime * 1000 > lastSyncAt.getTime()
367
+ );
368
+ if (newSigs.length === 0) continue;
369
+
370
+ const sigStrings = newSigs.map((s) => s.signature);
371
+ const transactions = await connection.getParsedTransactions(sigStrings, {
372
+ maxSupportedTransactionVersion: 0,
373
+ });
374
+
375
+ for (let i = 0; i < transactions.length; i++) {
376
+ const tx = transactions[i];
377
+ if (!tx?.meta || tx.meta.err) continue;
378
+
379
+ // Extract incoming SPL token transfers
380
+ const innerInstructions = tx.meta.innerInstructions || [];
381
+ const allInstructions = [
382
+ ...(tx.transaction.message.instructions || []),
383
+ ...innerInstructions.flatMap((ii: any) => ii.instructions || []),
384
+ ];
385
+
386
+ for (const ix of allInstructions) {
387
+ const parsed = (ix as any).parsed;
388
+ if (!parsed) continue;
389
+
390
+ // SPL token transfer/transferChecked
391
+ if (
392
+ (parsed.type === 'transfer' || parsed.type === 'transferChecked') &&
393
+ parsed.info
394
+ ) {
395
+ const dest = parsed.info.destination || parsed.info.account;
396
+ const mint = parsed.info.mint;
397
+ const amountStr = parsed.info.tokenAmount?.uiAmountString || parsed.info.amount;
398
+
399
+ // Only process if destination is our wallet's token account
400
+ // For SPL, we check post token balances to match owner
401
+ const postBalances = tx.meta.postTokenBalances || [];
402
+ const isOurWallet = postBalances.some(
403
+ (b: any) =>
404
+ b.owner === wallet.address &&
405
+ b.mint === mint
406
+ );
407
+
408
+ if (!isOurWallet || !mint) continue;
409
+
410
+ const amount = BigInt(parsed.info.amount || '0');
411
+ if (amount === 0n) continue;
412
+
413
+ try {
414
+ await processDiscoveredToken(
415
+ {
416
+ tokenAddress: mint,
417
+ walletAddress: wallet.address,
418
+ amount,
419
+ from: parsed.info.authority || parsed.info.source || '',
420
+ txHash: sigStrings[i],
421
+ blockNumber: BigInt(tx.slot),
422
+ },
423
+ chain,
424
+ ctx,
425
+ );
426
+ } catch (err) {
427
+ ctx.log.debug({ err, mint, wallet: wallet.address }, 'Discovery: failed Solana transfer');
428
+ }
429
+ }
430
+ }
431
+ }
432
+ } catch (err) {
433
+ ctx.log.warn({ err, chain, wallet: wallet.address }, 'Discovery: Solana wallet scan failed');
434
+ }
435
+ }
436
+
437
+ // Update sync state
438
+ await ctx.prisma.syncState.upsert({
439
+ where: { chain: syncKey },
440
+ create: {
441
+ chain: syncKey,
442
+ lastSyncAt: new Date(),
443
+ lastSyncStatus: 'ok',
444
+ syncCount: 1,
445
+ },
446
+ update: {
447
+ lastSyncAt: new Date(),
448
+ lastSyncStatus: 'ok',
449
+ lastError: null,
450
+ syncCount: { increment: 1 },
451
+ },
452
+ });
453
+ }
454
+
455
+ // ─── Viem chain detection (same approach as balance-sync) ──────────────
456
+
457
+ const EVM_CHAINS = new Set(['base', 'ethereum', 'arbitrum', 'optimism', 'polygon']);
458
+
459
+ // ─── Job Definition ───────────────────────────────────────────────────
460
+
461
+ export const incomingScanJob: CronJob = {
462
+ id: 'incoming-scan',
463
+ name: 'Incoming Asset Discovery',
464
+ intervalKey: 'discovery.scan_interval',
465
+ defaultInterval: 60_000,
466
+
467
+ async run(ctx: CronContext): Promise<void> {
468
+ const enabled = ctx.defaults.get<boolean>('discovery.enabled', true);
469
+ if (!enabled) return;
470
+
471
+ // Discover which chains have wallets
472
+ const chains = await ctx.prisma.hotWallet.groupBy({ by: ['chain'] });
473
+
474
+ for (const { chain } of chains) {
475
+ try {
476
+ if (isSolanaChain(chain)) {
477
+ await scanSolanaChain(chain, ctx);
478
+ } else if (EVM_CHAINS.has(chain)) {
479
+ await scanEvmChain(chain, ctx);
480
+ } else {
481
+ ctx.log.debug({ chain }, 'Discovery: skipping unknown chain');
482
+ }
483
+ } catch (err) {
484
+ ctx.log.error({ err, chain }, 'Discovery: scan failed for chain');
485
+
486
+ const syncKey = `${chain}:discovery`;
487
+ await ctx.prisma.syncState.upsert({
488
+ where: { chain: syncKey },
489
+ create: {
490
+ chain: syncKey,
491
+ lastSyncAt: new Date(),
492
+ lastSyncStatus: 'error',
493
+ lastError: getErrorMessage(err),
494
+ syncCount: 1,
495
+ },
496
+ update: {
497
+ lastSyncAt: new Date(),
498
+ lastSyncStatus: 'error',
499
+ lastError: getErrorMessage(err),
500
+ syncCount: { increment: 1 },
501
+ },
502
+ });
503
+ }
504
+ }
505
+ },
506
+ };
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Native Price Sync Job
3
+ * Fetches ETH and SOL USD prices from CoinGecko free API.
4
+ * One HTTP call, two DB upserts.
5
+ */
6
+
7
+ import type { CronJob, CronContext } from '../job';
8
+
9
+ const COINGECKO_URL =
10
+ 'https://api.coingecko.com/api/v3/simple/price?ids=ethereum,solana&vs_currencies=usd';
11
+
12
+ export const nativePriceJob: CronJob = {
13
+ id: 'native-price',
14
+ name: 'Native Price Sync',
15
+ intervalKey: 'sync.active_interval',
16
+ defaultInterval: 15_000,
17
+
18
+ async run(ctx: CronContext): Promise<void> {
19
+ const enabled = ctx.defaults.get<boolean>('sync.enabled', true);
20
+ if (!enabled) return;
21
+
22
+ let data: { ethereum?: { usd?: number }; solana?: { usd?: number } };
23
+
24
+ try {
25
+ const res = await fetch(COINGECKO_URL, {
26
+ signal: AbortSignal.timeout(10_000),
27
+ });
28
+ if (!res.ok) {
29
+ ctx.log.warn({ status: res.status }, 'CoinGecko returned non-OK status');
30
+ return;
31
+ }
32
+ data = await res.json();
33
+ } catch (err) {
34
+ ctx.log.warn({ err }, 'CoinGecko fetch failed (keeping stale price)');
35
+ return;
36
+ }
37
+
38
+ const ethPrice = data.ethereum?.usd;
39
+ const solPrice = data.solana?.usd;
40
+
41
+ const upserts: Promise<unknown>[] = [];
42
+
43
+ if (ethPrice != null) {
44
+ upserts.push(
45
+ ctx.prisma.nativePrice.upsert({
46
+ where: { currency: 'ETH' },
47
+ create: { currency: 'ETH', priceUsd: ethPrice.toString() },
48
+ update: { priceUsd: ethPrice.toString() },
49
+ })
50
+ );
51
+ }
52
+
53
+ if (solPrice != null) {
54
+ upserts.push(
55
+ ctx.prisma.nativePrice.upsert({
56
+ where: { currency: 'SOL' },
57
+ create: { currency: 'SOL', priceUsd: solPrice.toString() },
58
+ update: { priceUsd: solPrice.toString() },
59
+ })
60
+ );
61
+ }
62
+
63
+ await Promise.all(upserts);
64
+
65
+ ctx.log.debug(
66
+ { ethPrice, solPrice },
67
+ 'Native prices updated'
68
+ );
69
+ },
70
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Orphan Cleanup Job
3
+ * ------------------
4
+ * Rejects stale pending HumanAction records (strategy approvals, action tokens)
5
+ * that were left behind after crashes or restarts.
6
+ */
7
+
8
+ import type { CronJob, CronContext } from '../job';
9
+
10
+ const STALE_THRESHOLD_MS = 15 * 60_000; // 15 minutes
11
+
12
+ async function cleanOrphanedActions(ctx: CronContext): Promise<number> {
13
+ const staleThreshold = new Date(Date.now() - STALE_THRESHOLD_MS);
14
+ const cleaned = await ctx.prisma.humanAction.updateMany({
15
+ where: {
16
+ status: 'pending',
17
+ type: { in: ['strategy:approve', 'action'] },
18
+ createdAt: { lt: staleThreshold },
19
+ },
20
+ data: { status: 'rejected', resolvedAt: new Date() },
21
+ });
22
+ return cleaned.count;
23
+ }
24
+
25
+ export const orphanCleanupJob: CronJob = {
26
+ id: 'orphan-cleanup',
27
+ name: 'Orphan Cleanup',
28
+ intervalKey: 'cron.orphan_cleanup_interval',
29
+ defaultInterval: 5 * 60_000,
30
+
31
+ async setup(ctx) {
32
+ const count = await cleanOrphanedActions(ctx);
33
+ if (count > 0) ctx.log.warn({ count }, 'Cleaned orphaned pending actions on startup');
34
+ },
35
+
36
+ async run(ctx) {
37
+ const count = await cleanOrphanedActions(ctx);
38
+ if (count > 0) ctx.log.warn({ count }, 'Cleaned orphaned pending actions');
39
+ },
40
+ };