auramaxx 1.0.0-alpha.4

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 (363) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +112 -0
  3. package/bin/aurawallet.js +121 -0
  4. package/docs/ADAPTERS.md +467 -0
  5. package/docs/API.md +2679 -0
  6. package/docs/APPS.md +198 -0
  7. package/docs/ARCHITECTURE.md +350 -0
  8. package/docs/AUTH.md +698 -0
  9. package/docs/BEST-PRACTICES.md +121 -0
  10. package/docs/CLI.md +61 -0
  11. package/docs/DEVELOPING-APPS.md +452 -0
  12. package/docs/EXTENSION.md +97 -0
  13. package/docs/JOBS.md +33 -0
  14. package/docs/MCP.md +76 -0
  15. package/docs/PROTOCOL.md +142 -0
  16. package/docs/SETUP.md +219 -0
  17. package/docs/WORKSPACE.md +672 -0
  18. package/docs/agent-auth.md +63 -0
  19. package/docs/aura-file.md +48 -0
  20. package/docs/credentials.md +53 -0
  21. package/docs/external/getting-started.md +65 -0
  22. package/docs/external/overview.md +45 -0
  23. package/docs/external/use-cases.md +48 -0
  24. package/docs/external/why-aura.md +35 -0
  25. package/docs/jobs/connect-agent.md +77 -0
  26. package/docs/jobs/migrate-from-dotenv.md +79 -0
  27. package/docs/jobs/recover-from-lockout.md +72 -0
  28. package/docs/jobs/secure-ci.md +63 -0
  29. package/docs/oauth2.md +42 -0
  30. package/docs/passkeys.md +60 -0
  31. package/docs/security.md +540 -0
  32. package/docs/specs/aura-open-protocol.md +61 -0
  33. package/docs/specs/aura-provider-plugin.md +24 -0
  34. package/docs/specs/aura-registry-model.md +31 -0
  35. package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
  36. package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
  37. package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
  38. package/docs/specs/fixtures/valid-basic.aura +4 -0
  39. package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
  40. package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
  41. package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
  42. package/docs/totp.md +40 -0
  43. package/docs/wallet/AI.md +508 -0
  44. package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
  45. package/docs/wallet/README.md +47 -0
  46. package/docs/wallet/STRATEGY.md +89 -0
  47. package/next.config.ts +21 -0
  48. package/package.json +151 -0
  49. package/postcss.config.mjs +8 -0
  50. package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
  51. package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
  52. package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
  53. package/prisma/migrations/migration_lock.toml +3 -0
  54. package/prisma/schema.prisma +447 -0
  55. package/public/logo-chevron.svg +31 -0
  56. package/public/logo-concentric.svg +31 -0
  57. package/public/logo-crosshatch.svg +39 -0
  58. package/public/logo-dashed.svg +39 -0
  59. package/public/logo-horizontal.svg +31 -0
  60. package/public/logo-m56.svg +64 -0
  61. package/public/logo.webp +0 -0
  62. package/scripts/add-app.js +245 -0
  63. package/scripts/init.sh +57 -0
  64. package/scripts/migrate-apikeys-to-credentials.ts +35 -0
  65. package/scripts/sandbox-agent-flow.sh +235 -0
  66. package/scripts/sandbox.sh +175 -0
  67. package/scripts/validate-job-docs.mjs +125 -0
  68. package/server/abi/SwapHelper.json +438 -0
  69. package/server/cli/approval.ts +447 -0
  70. package/server/cli/commands/app.ts +204 -0
  71. package/server/cli/commands/cron.ts +24 -0
  72. package/server/cli/commands/doctor.ts +1007 -0
  73. package/server/cli/commands/env.ts +456 -0
  74. package/server/cli/commands/init.ts +752 -0
  75. package/server/cli/commands/mcp.ts +125 -0
  76. package/server/cli/commands/restore.ts +314 -0
  77. package/server/cli/commands/shell-hook.ts +468 -0
  78. package/server/cli/commands/start.ts +62 -0
  79. package/server/cli/commands/status.ts +59 -0
  80. package/server/cli/commands/stop.ts +14 -0
  81. package/server/cli/commands/token.ts +180 -0
  82. package/server/cli/commands/unlock.ts +49 -0
  83. package/server/cli/commands/vault.ts +417 -0
  84. package/server/cli/index.ts +328 -0
  85. package/server/cli/lib/aura-parser.ts +64 -0
  86. package/server/cli/lib/credential-create.ts +74 -0
  87. package/server/cli/lib/credential-resolve.ts +254 -0
  88. package/server/cli/lib/dotenv-migrate.ts +116 -0
  89. package/server/cli/lib/dotenv-parser.ts +146 -0
  90. package/server/cli/lib/http.ts +91 -0
  91. package/server/cli/lib/init-steps.ts +76 -0
  92. package/server/cli/lib/local-agent-trust.ts +45 -0
  93. package/server/cli/lib/process.ts +136 -0
  94. package/server/cli/lib/prompt.ts +85 -0
  95. package/server/cli/lib/theme.ts +240 -0
  96. package/server/cli/socket.ts +570 -0
  97. package/server/cli/transport-client.ts +50 -0
  98. package/server/cron/index.ts +137 -0
  99. package/server/cron/job.ts +31 -0
  100. package/server/cron/jobs/balance-sync.ts +436 -0
  101. package/server/cron/jobs/incoming-scan.ts +506 -0
  102. package/server/cron/jobs/native-price.ts +70 -0
  103. package/server/cron/jobs/orphan-cleanup.ts +40 -0
  104. package/server/cron/jobs/strategy-runner.ts +175 -0
  105. package/server/cron/scheduler.ts +125 -0
  106. package/server/index.ts +406 -0
  107. package/server/lib/adapters/factory.ts +110 -0
  108. package/server/lib/adapters/index.ts +19 -0
  109. package/server/lib/adapters/router.ts +297 -0
  110. package/server/lib/adapters/telegram.ts +645 -0
  111. package/server/lib/adapters/types.ts +89 -0
  112. package/server/lib/adapters/webhook.ts +95 -0
  113. package/server/lib/address.ts +49 -0
  114. package/server/lib/agent-auth/contracts.ts +1194 -0
  115. package/server/lib/agent-profiles.ts +328 -0
  116. package/server/lib/ai.ts +285 -0
  117. package/server/lib/api-registry/contracts.ts +86 -0
  118. package/server/lib/api-registry/validation.ts +172 -0
  119. package/server/lib/apikey-migration.ts +189 -0
  120. package/server/lib/app-installer.ts +505 -0
  121. package/server/lib/app-tokens.ts +247 -0
  122. package/server/lib/auth.ts +314 -0
  123. package/server/lib/batch.ts +242 -0
  124. package/server/lib/cold.ts +874 -0
  125. package/server/lib/config.ts +381 -0
  126. package/server/lib/credential-access-audit.ts +85 -0
  127. package/server/lib/credential-access-policy.ts +110 -0
  128. package/server/lib/credential-health.ts +343 -0
  129. package/server/lib/credential-import.ts +487 -0
  130. package/server/lib/credential-scope.ts +87 -0
  131. package/server/lib/credential-shares.ts +190 -0
  132. package/server/lib/credential-transport.ts +342 -0
  133. package/server/lib/credential-vault.ts +77 -0
  134. package/server/lib/credentials.ts +333 -0
  135. package/server/lib/crypto.ts +8 -0
  136. package/server/lib/db.ts +15 -0
  137. package/server/lib/defaults.ts +366 -0
  138. package/server/lib/dex/index.ts +80 -0
  139. package/server/lib/dex/relay.ts +235 -0
  140. package/server/lib/dex/types.ts +59 -0
  141. package/server/lib/dex/uniswap.ts +370 -0
  142. package/server/lib/e2e-agent/artifacts.ts +36 -0
  143. package/server/lib/e2e-agent/contracts.ts +112 -0
  144. package/server/lib/e2e-agent/validation.ts +135 -0
  145. package/server/lib/encrypt.ts +128 -0
  146. package/server/lib/error.ts +20 -0
  147. package/server/lib/events.ts +205 -0
  148. package/server/lib/hot.ts +357 -0
  149. package/server/lib/key-fingerprint.ts +28 -0
  150. package/server/lib/logger.ts +331 -0
  151. package/server/lib/network.ts +137 -0
  152. package/server/lib/notifications.ts +219 -0
  153. package/server/lib/oauth2-refresh.ts +241 -0
  154. package/server/lib/oursecret.ts +54 -0
  155. package/server/lib/passkey-credential.ts +360 -0
  156. package/server/lib/passkey.ts +68 -0
  157. package/server/lib/permissions.ts +248 -0
  158. package/server/lib/pino.ts +24 -0
  159. package/server/lib/policy-preview.ts +138 -0
  160. package/server/lib/price.ts +338 -0
  161. package/server/lib/prices.ts +34 -0
  162. package/server/lib/project-scope.ts +239 -0
  163. package/server/lib/resolve-action.ts +427 -0
  164. package/server/lib/resolve.ts +36 -0
  165. package/server/lib/sessions.ts +632 -0
  166. package/server/lib/solana/connection.ts +26 -0
  167. package/server/lib/solana/jupiter.ts +128 -0
  168. package/server/lib/solana/transfer.ts +108 -0
  169. package/server/lib/solana/wallet.ts +136 -0
  170. package/server/lib/strategy/emits.ts +21 -0
  171. package/server/lib/strategy/engine.ts +1305 -0
  172. package/server/lib/strategy/executor.ts +115 -0
  173. package/server/lib/strategy/hook-context.ts +158 -0
  174. package/server/lib/strategy/hooks.ts +990 -0
  175. package/server/lib/strategy/index.ts +28 -0
  176. package/server/lib/strategy/installer.ts +305 -0
  177. package/server/lib/strategy/loader.ts +256 -0
  178. package/server/lib/strategy/message.ts +235 -0
  179. package/server/lib/strategy/repository.ts +218 -0
  180. package/server/lib/strategy/session-logger.ts +693 -0
  181. package/server/lib/strategy/sources.ts +288 -0
  182. package/server/lib/strategy/state.ts +189 -0
  183. package/server/lib/strategy/templates.ts +403 -0
  184. package/server/lib/strategy/tick.ts +404 -0
  185. package/server/lib/strategy/types.ts +230 -0
  186. package/server/lib/swap.ts +3 -0
  187. package/server/lib/temp.ts +86 -0
  188. package/server/lib/token-metadata.ts +86 -0
  189. package/server/lib/token-safety.ts +200 -0
  190. package/server/lib/token-search.ts +444 -0
  191. package/server/lib/totp.ts +194 -0
  192. package/server/lib/transactions.ts +123 -0
  193. package/server/lib/transport.ts +75 -0
  194. package/server/lib/txhistory/decoder.ts +262 -0
  195. package/server/lib/txhistory/enricher.ts +652 -0
  196. package/server/lib/txhistory/index.ts +391 -0
  197. package/server/lib/txhistory/signatures.ts +59 -0
  198. package/server/lib/verified-summary.ts +421 -0
  199. package/server/mcp/profile-policy.ts +30 -0
  200. package/server/mcp/server.ts +619 -0
  201. package/server/mcp/tools.ts +523 -0
  202. package/server/middleware/auth.ts +119 -0
  203. package/server/middleware/requestLogger.ts +84 -0
  204. package/server/routes/actions.ts +459 -0
  205. package/server/routes/adapters.ts +703 -0
  206. package/server/routes/addressbook.ts +113 -0
  207. package/server/routes/ai.ts +34 -0
  208. package/server/routes/apikeys.ts +295 -0
  209. package/server/routes/apps.ts +601 -0
  210. package/server/routes/auth.ts +457 -0
  211. package/server/routes/backup.ts +340 -0
  212. package/server/routes/batch.ts +270 -0
  213. package/server/routes/bookmarks.ts +162 -0
  214. package/server/routes/credential-shares.ts +198 -0
  215. package/server/routes/credential-vaults.ts +154 -0
  216. package/server/routes/credentials.ts +1290 -0
  217. package/server/routes/dashboard.ts +71 -0
  218. package/server/routes/defaults.ts +124 -0
  219. package/server/routes/fund.ts +229 -0
  220. package/server/routes/import.ts +352 -0
  221. package/server/routes/launch.ts +665 -0
  222. package/server/routes/lock.ts +54 -0
  223. package/server/routes/logs.ts +68 -0
  224. package/server/routes/nuke.ts +111 -0
  225. package/server/routes/passkey-credentials.ts +99 -0
  226. package/server/routes/passkey.ts +346 -0
  227. package/server/routes/portfolio.ts +217 -0
  228. package/server/routes/price.ts +63 -0
  229. package/server/routes/resolve.ts +31 -0
  230. package/server/routes/security.ts +45 -0
  231. package/server/routes/send-evm.ts +241 -0
  232. package/server/routes/send-solana.ts +281 -0
  233. package/server/routes/send.ts +178 -0
  234. package/server/routes/setup.ts +210 -0
  235. package/server/routes/strategy.ts +894 -0
  236. package/server/routes/swap-evm.ts +353 -0
  237. package/server/routes/swap-solana.ts +177 -0
  238. package/server/routes/swap.ts +356 -0
  239. package/server/routes/token.ts +247 -0
  240. package/server/routes/unlock.ts +403 -0
  241. package/server/routes/wallet-assets.ts +361 -0
  242. package/server/routes/wallet-transactions.ts +515 -0
  243. package/server/routes/wallet.ts +710 -0
  244. package/server/types.ts +146 -0
  245. package/skills/aurawallet/SKILL.md +739 -0
  246. package/skills/aurawallet-setup/SKILL.md +74 -0
  247. package/skills/security-review/SKILL.md +148 -0
  248. package/src/app/api/agent-requests/route.ts +30 -0
  249. package/src/app/api/apps/install/route.ts +126 -0
  250. package/src/app/api/apps/manifests/route.ts +16 -0
  251. package/src/app/api/apps/static/[...path]/route.ts +57 -0
  252. package/src/app/api/events/route.ts +92 -0
  253. package/src/app/api/page.tsx +212 -0
  254. package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
  255. package/src/app/api/workspace/[id]/apps/route.ts +81 -0
  256. package/src/app/api/workspace/[id]/export/route.ts +67 -0
  257. package/src/app/api/workspace/[id]/route.ts +168 -0
  258. package/src/app/api/workspace/auth.ts +34 -0
  259. package/src/app/api/workspace/config/route.ts +106 -0
  260. package/src/app/api/workspace/import/route.ts +127 -0
  261. package/src/app/api/workspace/route.ts +116 -0
  262. package/src/app/app/page.tsx +2122 -0
  263. package/src/app/apple-icon.png +0 -0
  264. package/src/app/docs/page.tsx +178 -0
  265. package/src/app/favicon.ico +0 -0
  266. package/src/app/globals.css +572 -0
  267. package/src/app/health/page.tsx +5 -0
  268. package/src/app/hello/page.tsx +15 -0
  269. package/src/app/icon.png +0 -0
  270. package/src/app/layout.tsx +34 -0
  271. package/src/app/page.tsx +986 -0
  272. package/src/app/providers.tsx +90 -0
  273. package/src/app/share/[token]/page.tsx +295 -0
  274. package/src/components/ChainSelector.tsx +144 -0
  275. package/src/components/HumanActionBar.tsx +695 -0
  276. package/src/components/NotificationDrawer.tsx +129 -0
  277. package/src/components/apps/AgentKeysApp.tsx +490 -0
  278. package/src/components/apps/App.tsx +153 -0
  279. package/src/components/apps/AppGrid.tsx +15 -0
  280. package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
  281. package/src/components/apps/DraggableApp.tsx +562 -0
  282. package/src/components/apps/IFrameApp.tsx +73 -0
  283. package/src/components/apps/LogsApp.tsx +360 -0
  284. package/src/components/apps/SendApp.tsx +394 -0
  285. package/src/components/apps/SetupWizardApp.tsx +1004 -0
  286. package/src/components/apps/SystemDefaultsApp.tsx +845 -0
  287. package/src/components/apps/ThirdPartyApp.tsx +428 -0
  288. package/src/components/apps/TokenApp.tsx +319 -0
  289. package/src/components/apps/TransactionsApp.tsx +438 -0
  290. package/src/components/apps/WalletDetailApp.tsx +1505 -0
  291. package/src/components/apps/index.ts +13 -0
  292. package/src/components/design-system/Button.tsx +53 -0
  293. package/src/components/design-system/ChainIndicator.tsx +65 -0
  294. package/src/components/design-system/ChainSelector.tsx +137 -0
  295. package/src/components/design-system/ConfirmationModal.tsx +106 -0
  296. package/src/components/design-system/ConfirmationPopover.tsx +81 -0
  297. package/src/components/design-system/Drawer.tsx +123 -0
  298. package/src/components/design-system/FilterDropdown.tsx +72 -0
  299. package/src/components/design-system/Modal.tsx +206 -0
  300. package/src/components/design-system/Popover.tsx +142 -0
  301. package/src/components/design-system/TextInput.tsx +85 -0
  302. package/src/components/design-system/Toggle.tsx +58 -0
  303. package/src/components/design-system/index.ts +11 -0
  304. package/src/components/docs/DocsThemeToggle.tsx +49 -0
  305. package/src/components/health/CredentialHealthDashboard.tsx +214 -0
  306. package/src/components/icons/ChainIcons.tsx +72 -0
  307. package/src/components/layout/AppStoreDrawer.tsx +369 -0
  308. package/src/components/layout/ContentArea.tsx +21 -0
  309. package/src/components/layout/TabBar.tsx +278 -0
  310. package/src/components/layout/WalletSidebar.tsx +1033 -0
  311. package/src/components/layout/index.ts +4 -0
  312. package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
  313. package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
  314. package/src/components/vault/ApiKeysConsole.tsx +1080 -0
  315. package/src/components/vault/AuditConsole.tsx +584 -0
  316. package/src/components/vault/CredentialDetail.tsx +455 -0
  317. package/src/components/vault/CredentialEmpty.tsx +55 -0
  318. package/src/components/vault/CredentialField.tsx +361 -0
  319. package/src/components/vault/CredentialForm.tsx +1212 -0
  320. package/src/components/vault/CredentialList.tsx +165 -0
  321. package/src/components/vault/CredentialRow.tsx +97 -0
  322. package/src/components/vault/CredentialShareModal.tsx +178 -0
  323. package/src/components/vault/CredentialVault.tsx +754 -0
  324. package/src/components/vault/CredentialWalletWidget.tsx +103 -0
  325. package/src/components/vault/ImportCredentialsModal.tsx +515 -0
  326. package/src/components/vault/LargeTypeModal.tsx +64 -0
  327. package/src/components/vault/PasswordGenerator.tsx +224 -0
  328. package/src/components/vault/TOTPDisplay.tsx +123 -0
  329. package/src/components/vault/VaultSidebar.tsx +413 -0
  330. package/src/components/vault/types.ts +54 -0
  331. package/src/context/AuthContext.tsx +337 -0
  332. package/src/context/PriceContext.tsx +113 -0
  333. package/src/context/ThemeContext.tsx +164 -0
  334. package/src/context/WebSocketContext.tsx +269 -0
  335. package/src/context/WorkspaceContext.tsx +668 -0
  336. package/src/hooks/index.ts +3 -0
  337. package/src/hooks/useAgentActions.ts +368 -0
  338. package/src/hooks/useBalance.ts +103 -0
  339. package/src/hooks/useBalances.ts +129 -0
  340. package/src/instrumentation.ts +12 -0
  341. package/src/lib/api.ts +449 -0
  342. package/src/lib/app-loader.ts +148 -0
  343. package/src/lib/app-registry.ts +178 -0
  344. package/src/lib/app-sdk.ts +157 -0
  345. package/src/lib/audit-console-adapter.ts +151 -0
  346. package/src/lib/auth-client.ts +75 -0
  347. package/src/lib/config.ts +74 -0
  348. package/src/lib/crypto.ts +112 -0
  349. package/src/lib/db.ts +21 -0
  350. package/src/lib/docs.ts +390 -0
  351. package/src/lib/events.ts +361 -0
  352. package/src/lib/pino.ts +24 -0
  353. package/src/lib/theme-handlers.ts +168 -0
  354. package/src/lib/theme.ts +351 -0
  355. package/src/lib/tokenData.ts +378 -0
  356. package/src/lib/vault-crypto.ts +129 -0
  357. package/src/lib/websocket-server.ts +302 -0
  358. package/src/lib/websocket-setup.ts +79 -0
  359. package/src/lib/wordlist.ts +2050 -0
  360. package/src/lib/workspace-handlers.ts +285 -0
  361. package/start.sh +80 -0
  362. package/tailwind.config.ts +99 -0
  363. package/tsconfig.json +42 -0
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Lightweight request/response logging middleware using Pino
3
+ *
4
+ * Console logging via Pino (structured, with request IDs and timing).
5
+ * Only stores events in DB for errors and security-relevant failures (4xx/5xx).
6
+ * Business events (send, fund, swap, token create, etc.) are logged separately
7
+ * by each route via the logger module, which handles DB + WebSocket storage.
8
+ */
9
+
10
+ import { randomBytes } from 'crypto';
11
+ import { Request, Response, NextFunction } from 'express';
12
+ import { log } from '../lib/pino';
13
+ import { events } from '../lib/events';
14
+
15
+ // Paths to skip logging entirely (high-frequency/low-value)
16
+ const SKIP_PATHS = new Set(['/health']);
17
+
18
+ /**
19
+ * Express middleware that logs request/response details via Pino
20
+ * Only persists error/security events to DB to avoid bloat
21
+ */
22
+ export function requestLogger(req: Request, res: Response, next: NextFunction): void {
23
+ if (SKIP_PATHS.has(req.path)) {
24
+ next();
25
+ return;
26
+ }
27
+
28
+ const requestId = randomBytes(4).toString('hex');
29
+ const startTime = process.hrtime.bigint();
30
+
31
+ const child = log.child({ requestId });
32
+
33
+ child.debug({
34
+ method: req.method,
35
+ url: req.originalUrl || req.path,
36
+ }, 'request start');
37
+
38
+ res.on('finish', () => {
39
+ const durationNs = process.hrtime.bigint() - startTime;
40
+ const durationMs = Number(durationNs) / 1_000_000;
41
+ const statusCode = res.statusCode;
42
+
43
+ const logData: Record<string, unknown> = {
44
+ method: req.method,
45
+ url: req.originalUrl || req.path,
46
+ statusCode,
47
+ durationMs: Math.round(durationMs * 100) / 100,
48
+ };
49
+
50
+ // Add agent identification for authenticated requests
51
+ if (req.auth) {
52
+ logData.agentId = req.auth.token.agentId;
53
+ }
54
+
55
+ // Log level based on status code
56
+ if (statusCode >= 500) {
57
+ child.error(logData, 'request error');
58
+ } else if (statusCode >= 400) {
59
+ child.warn(logData, 'request complete');
60
+ } else {
61
+ child.debug(logData, 'request complete');
62
+ }
63
+
64
+ // Only persist security-relevant failures to DB (auth failures, forbidden, rate limits, server errors)
65
+ if (statusCode === 401 || statusCode === 403 || statusCode === 429 || statusCode >= 500) {
66
+ const eventType =
67
+ statusCode === 401 ? 'request:auth_failed' :
68
+ statusCode === 403 ? 'request:forbidden' :
69
+ statusCode === 429 ? 'request:rate_limited' :
70
+ 'request:server_error';
71
+
72
+ events.custom(eventType, {
73
+ requestId,
74
+ method: req.method,
75
+ path: req.originalUrl || req.path,
76
+ statusCode,
77
+ durationMs: Math.round(durationMs * 100) / 100,
78
+ agentId: req.auth?.token?.agentId,
79
+ });
80
+ }
81
+ });
82
+
83
+ next();
84
+ }
@@ -0,0 +1,459 @@
1
+ import { Router, Request, Response } from 'express';
2
+ import { randomBytes } from 'crypto';
3
+ import { prisma } from '../lib/db';
4
+ import { events } from '../lib/events';
5
+ import { requireWalletAuth } from '../middleware/auth';
6
+ import { requireAdmin, isAdmin, requirePermission } from '../lib/permissions';
7
+ import { createToken, getTokenHash, type AgentTokenPayload } from '../lib/auth';
8
+ import { isValidAgentPubkey, normalizeAgentPubkey, encryptToAgentPubkey } from '../lib/credential-transport';
9
+ import { hashSecret } from '../lib/crypto';
10
+ import { isUnlocked } from '../lib/cold';
11
+ import { normalizeAddress } from '../lib/address';
12
+ import { generateVerifiedSummary } from '../lib/verified-summary';
13
+ import { listTokensFromDb, revokeToken } from '../lib/sessions';
14
+ import { createHumanActionNotification, createNotification } from '../lib/notifications';
15
+ import { getDefault } from '../lib/defaults';
16
+ import { logger } from '../lib/logger';
17
+ import { getErrorMessage } from '../lib/error';
18
+ import { resolveAction } from '../lib/resolve-action';
19
+ import { AgentProfileError, resolveProfileToEffectivePolicy } from '../lib/agent-profiles';
20
+ import { buildPolicyPreviewV1, mapPreviewError } from '../lib/policy-preview';
21
+
22
+ const router = Router();
23
+
24
+ // GET /actions/pending — List all pending human actions
25
+ router.get('/pending', requireWalletAuth, requirePermission('action:read'), async (_req: Request, res: Response) => {
26
+ try {
27
+ const actions = await prisma.humanAction.findMany({
28
+ where: {
29
+ status: 'pending',
30
+ NOT: { type: 'strategy:message' },
31
+ },
32
+ orderBy: { createdAt: 'desc' },
33
+ });
34
+ res.json({ success: true, actions });
35
+ } catch (error) {
36
+ const message = getErrorMessage(error);
37
+ res.status(500).json({ success: false, error: message });
38
+ }
39
+ });
40
+
41
+ // POST /actions — Create a human action request (app proposes an action for approval)
42
+ // Requires Bearer token with action:create permission
43
+ router.post('/', requireWalletAuth, requirePermission('action:create'), async (req: Request, res: Response) => {
44
+ try {
45
+ const { summary, permissions, limits, walletAccess, ttl, type, metadata, notify, pubkey, credentialAccess } = req.body;
46
+
47
+ // Validate summary (required for all types)
48
+ if (!summary || typeof summary !== 'string' || summary.trim().length === 0) {
49
+ res.status(400).json({ success: false, error: 'summary is required and must be a non-empty string' });
50
+ return;
51
+ }
52
+
53
+ const MAX_SUMMARY_LENGTH = 500;
54
+ if (summary.length > MAX_SUMMARY_LENGTH) {
55
+ res.status(400).json({ success: false, error: `summary must be ${MAX_SUMMARY_LENGTH} characters or fewer` });
56
+ return;
57
+ }
58
+
59
+ const callerAgentId = req.auth!.token.agentId;
60
+
61
+ // === Notification-only branch: no permissions/limits needed ===
62
+ if (type === 'notify') {
63
+ const request = await prisma.humanAction.create({
64
+ data: {
65
+ type: 'notify',
66
+ fromTier: 'system',
67
+ toAddress: null,
68
+ amount: null,
69
+ chain: 'base',
70
+ status: 'acknowledged',
71
+ resolvedAt: new Date(),
72
+ metadata: JSON.stringify({ agentId: callerAgentId, summary, ...(metadata || {}) }),
73
+ },
74
+ });
75
+
76
+ // Info notification (dismiss only, no approve/reject)
77
+ await createNotification({
78
+ type: 'info',
79
+ category: 'general',
80
+ title: 'Notification',
81
+ message: summary,
82
+ actions: [{ id: 'dismiss', label: 'DISMISS', type: 'secondary', action: 'dismiss' }],
83
+ metadata: { ...(metadata || {}), agentId: callerAgentId },
84
+ source: 'agent',
85
+ agentId: callerAgentId,
86
+ });
87
+
88
+ // Emit to WebSocket for dashboard; adapters check type themselves
89
+ if (notify !== false) {
90
+ events.actionCreated({
91
+ id: request.id,
92
+ type: 'notify',
93
+ source: `agent:${callerAgentId}`,
94
+ summary,
95
+ expiresAt: null,
96
+ metadata: { agentId: callerAgentId, ...(metadata || {}) },
97
+ });
98
+ }
99
+
100
+ logger.actionCreated(callerAgentId, request.id, 'notify', summary);
101
+
102
+ res.json({ success: true, id: request.id });
103
+ return;
104
+ }
105
+
106
+ // === Standard approval flow: permissions required ===
107
+
108
+ // Validate permissions
109
+ if (!permissions || !Array.isArray(permissions) || permissions.length === 0) {
110
+ res.status(400).json({ success: false, error: 'permissions must be a non-empty array' });
111
+ return;
112
+ }
113
+
114
+ // Block privilege escalation — cannot request admin:* or action:create
115
+ const blocked = permissions.filter((p: string) => p === 'admin:*' || p === 'action:create');
116
+ if (blocked.length > 0) {
117
+ res.status(400).json({ success: false, error: `Cannot request privileged permissions: ${blocked.join(', ')}` });
118
+ return;
119
+ }
120
+
121
+ const requestedCredentialAccess = credentialAccess && typeof credentialAccess === 'object' && !Array.isArray(credentialAccess)
122
+ ? credentialAccess as AgentTokenPayload['credentialAccess']
123
+ : undefined;
124
+ if (typeof pubkey !== 'string' || !pubkey.trim()) {
125
+ res.status(400).json({ success: false, error: 'pubkey is required' });
126
+ return;
127
+ }
128
+ if (!isValidAgentPubkey(pubkey)) {
129
+ res.status(400).json({ success: false, error: 'pubkey must be a valid RSA public key (PEM or base64)' });
130
+ return;
131
+ }
132
+ const normalizedPubkey = normalizeAgentPubkey(pubkey);
133
+
134
+ const defaultActionTtl = await getDefault<number>('ttl.action', 60);
135
+ const actionTtl = typeof ttl === 'number' && ttl > 0 ? ttl : defaultActionTtl;
136
+
137
+ // Generate secret for polling (same pattern as POST /auth)
138
+ const secret = randomBytes(32).toString('hex');
139
+ const secretHash = hashSecret(secret);
140
+
141
+ // Preserve pre-computed action from metadata if provided (for auto-execute on approval)
142
+ const precomputedAction = metadata?.action || undefined;
143
+
144
+ // Generate server-verified summary from actual action parameters
145
+ const verifiedSummary = generateVerifiedSummary({
146
+ agentId: callerAgentId,
147
+ summary,
148
+ permissions,
149
+ limits: limits || undefined,
150
+ walletAccess: walletAccess || undefined,
151
+ ttl: actionTtl,
152
+ action: precomputedAction,
153
+ });
154
+
155
+ const request = await prisma.humanAction.create({
156
+ data: {
157
+ type: 'action',
158
+ fromTier: 'system',
159
+ toAddress: null,
160
+ amount: null,
161
+ chain: 'base',
162
+ status: 'pending',
163
+ metadata: JSON.stringify({
164
+ agentId: callerAgentId,
165
+ permissions,
166
+ limits: limits || undefined,
167
+ walletAccess: walletAccess || undefined,
168
+ credentialAccess: requestedCredentialAccess,
169
+ pubkey: normalizedPubkey,
170
+ ttl: actionTtl,
171
+ secretHash,
172
+ summary,
173
+ action: precomputedAction,
174
+ verifiedSummary,
175
+ }),
176
+ },
177
+ });
178
+
179
+ // Create notification for human approval
180
+ await createHumanActionNotification(request);
181
+
182
+ // Emit WebSocket event
183
+ events.actionCreated({
184
+ id: request.id,
185
+ type: 'action',
186
+ source: `agent:${callerAgentId}`,
187
+ summary,
188
+ expiresAt: null,
189
+ metadata: { agentId: callerAgentId, permissions, limits, summary, verifiedSummary },
190
+ });
191
+
192
+ logger.actionCreated(callerAgentId, request.id, 'action', summary);
193
+
194
+ res.json({
195
+ success: true,
196
+ requestId: request.id,
197
+ secret,
198
+ message: 'Waiting for human approval',
199
+ });
200
+ } catch (error) {
201
+ const message = getErrorMessage(error);
202
+ res.status(500).json({ success: false, error: message });
203
+ }
204
+ });
205
+
206
+ // POST /actions/:id/resolve — Approve or reject a human action
207
+ router.post('/:id/resolve', requireWalletAuth, requirePermission('action:resolve'), async (req: Request<{ id: string }>, res: Response) => {
208
+ try {
209
+ const { approved, walletAccess, limits } = req.body;
210
+ const result = await resolveAction(req.params.id, approved, { walletAccess, limits });
211
+ res.status(result.statusCode).json(result.data);
212
+ } catch (error) {
213
+ const message = getErrorMessage(error);
214
+ res.status(500).json({ success: false, error: message });
215
+ }
216
+ });
217
+
218
+ // POST /actions/token/preview - Preview effective token policy without issuing token (requires admin)
219
+ router.post('/token/preview', requireWalletAuth, requireAdmin, async (req: Request, res: Response) => {
220
+ try {
221
+ const { profile, profileVersion, profileOverrides } = req.body;
222
+
223
+ if (typeof profile !== 'string' || profile.trim().length === 0) {
224
+ res.status(422).json({ version: 'v1', code: 'ERR_OVERRIDE_INVALID', error: 'profile is required' });
225
+ return;
226
+ }
227
+
228
+ const previewInput = {
229
+ profileId: profile,
230
+ profileVersion: typeof profileVersion === 'string' ? profileVersion : undefined,
231
+ overrides: profileOverrides,
232
+ };
233
+
234
+ const resolved = resolveProfileToEffectivePolicy(previewInput);
235
+ const preview = buildPolicyPreviewV1(previewInput, resolved);
236
+
237
+ res.json(preview);
238
+ } catch (error) {
239
+ if (error instanceof AgentProfileError) {
240
+ const mapped = mapPreviewError(error.code);
241
+ res.status(mapped.status).json(mapped.error);
242
+ return;
243
+ }
244
+ const mapped = mapPreviewError('ERR_RESOLUTION_FAILED');
245
+ res.status(mapped.status).json(mapped.error);
246
+ }
247
+ });
248
+
249
+ // POST /actions/token - Create signed token for agent (requires admin)
250
+ router.post('/token', requireWalletAuth, requireAdmin, async (req: Request, res: Response) => {
251
+ try {
252
+ const {
253
+ agentId,
254
+ limit,
255
+ permissions,
256
+ ttl,
257
+ limits, // Per-permission limits
258
+ walletAccess, // Wallet access grants
259
+ credentialAccess,
260
+ pubkey,
261
+ profile,
262
+ profileVersion,
263
+ profileOverrides,
264
+ } = req.body;
265
+
266
+ if (!agentId || typeof agentId !== 'string') {
267
+ res.status(400).json({ error: 'agentId is required' });
268
+ return;
269
+ }
270
+
271
+ // Legacy limit or new limits.fund
272
+ const fundLimit = typeof limit === 'number' ? limit : (limits?.fund ?? 0);
273
+ if (fundLimit < 0) {
274
+ res.status(400).json({ error: 'limit must be a non-negative number (in ETH)' });
275
+ return;
276
+ }
277
+
278
+ // Wallet must be unlocked
279
+ if (!isUnlocked()) {
280
+ res.status(401).json({ error: 'Wallet is locked. Unlock first.' });
281
+ return;
282
+ }
283
+
284
+ const defaultSendLimit = await getDefault<number>('limits.send', 0.1);
285
+ const defaultSwapLimit = await getDefault<number>('limits.swap', 0.1);
286
+ const defaultPermissions = await getDefault<string[]>('permissions.default', ['wallet:create:hot', 'send:hot', 'swap', 'fund', 'action:create']);
287
+ const defaultTtl = await getDefault<number>('ttl.agent', 3600);
288
+
289
+ const resolvedProfile = typeof profile === 'string' && profile.trim().length > 0
290
+ ? resolveProfileToEffectivePolicy({
291
+ profileId: profile,
292
+ profileVersion: typeof profileVersion === 'string' ? profileVersion : undefined,
293
+ overrides: profileOverrides,
294
+ })
295
+ : null;
296
+
297
+ const validPermissions = resolvedProfile
298
+ ? [...resolvedProfile.permissions]
299
+ : (Array.isArray(permissions) ? [...permissions] : [...defaultPermissions]);
300
+
301
+ if (typeof pubkey !== 'string' || !pubkey.trim()) {
302
+ res.status(400).json({ error: 'pubkey is required' });
303
+ return;
304
+ }
305
+ if (!isValidAgentPubkey(pubkey)) {
306
+ res.status(400).json({ error: 'pubkey must be a valid RSA public key (PEM or base64)' });
307
+ return;
308
+ }
309
+ const normalizedPubkey = normalizeAgentPubkey(pubkey);
310
+
311
+ const ttlSeconds = resolvedProfile
312
+ ? resolvedProfile.ttlSeconds
313
+ : (typeof ttl === 'number' ? ttl : defaultTtl);
314
+
315
+ // Normalize wallet access addresses
316
+ const normalizedWalletAccess = walletAccess && Array.isArray(walletAccess)
317
+ ? walletAccess.map((addr: string) => normalizeAddress(addr))
318
+ : undefined;
319
+
320
+ // Build limits: per-token overrides > system defaults
321
+ const baseLimits = { fund: fundLimit, send: defaultSendLimit, swap: defaultSwapLimit };
322
+ const tokenLimits = limits ? { ...baseLimits, ...limits } : baseLimits;
323
+
324
+ const effectiveCredentialAccess = resolvedProfile
325
+ ? resolvedProfile.credentialAccess
326
+ : credentialAccess;
327
+
328
+ const token = await createToken(agentId, fundLimit, validPermissions, ttlSeconds, {
329
+ limits: tokenLimits,
330
+ walletAccess: normalizedWalletAccess,
331
+ credentialAccess: effectiveCredentialAccess,
332
+ agentPubkey: normalizedPubkey,
333
+ });
334
+
335
+ const tokenHash = getTokenHash(token);
336
+
337
+ // Emit WebSocket event for direct token creation
338
+ events.tokenCreated({
339
+ tokenHash,
340
+ agentId,
341
+ limit: fundLimit,
342
+ permissions: validPermissions,
343
+ expiresAt: Date.now() + ttlSeconds * 1000,
344
+ });
345
+
346
+ if (resolvedProfile) {
347
+ events.custom('agent_profile:issued', {
348
+ eventSchemaVersion: 1,
349
+ eventType: 'agent_profile.issued',
350
+ profile: resolvedProfile.profile,
351
+ effectivePolicyHash: resolvedProfile.effectivePolicyHash,
352
+ overrideDelta: resolvedProfile.overrideDelta,
353
+ actor: 'admin',
354
+ agentId,
355
+ tokenHash,
356
+ timestamp: Date.now(),
357
+ });
358
+ }
359
+
360
+ // Encrypt token to agent pubkey if provided (prevents model provider from seeing it)
361
+ const responseToken = normalizedPubkey
362
+ ? { encryptedToken: encryptToAgentPubkey(token, normalizedPubkey) }
363
+ : { token };
364
+
365
+ res.json({
366
+ success: true,
367
+ ...responseToken,
368
+ agentId,
369
+ limit: fundLimit,
370
+ limits: tokenLimits,
371
+ permissions: validPermissions,
372
+ walletAccess: normalizedWalletAccess,
373
+ credentialAccess: effectiveCredentialAccess,
374
+ profile: resolvedProfile ? resolvedProfile.profile : undefined,
375
+ effectivePolicyHash: resolvedProfile ? resolvedProfile.effectivePolicyHash : undefined,
376
+ overrideDelta: resolvedProfile ? resolvedProfile.overrideDelta : undefined,
377
+ warnings: resolvedProfile ? resolvedProfile.warnings : undefined,
378
+ hasPubkey: !!normalizedPubkey,
379
+ expiresIn: ttlSeconds
380
+ });
381
+ } catch (error) {
382
+ if (error instanceof AgentProfileError) {
383
+ res.status(400).json({ error: error.message, code: error.code });
384
+ return;
385
+ }
386
+ const message = getErrorMessage(error);
387
+ res.status(400).json({ error: message });
388
+ }
389
+ });
390
+
391
+ // GET /actions/tokens - List all agent tokens (requires admin)
392
+ router.get('/tokens', requireWalletAuth, requireAdmin, async (_req: Request, res: Response) => {
393
+ try {
394
+ const tokens = await listTokensFromDb();
395
+
396
+ const active = tokens.filter(t => t.isActive && !t.isExpired && !t.isRevoked && t.remaining > 0);
397
+ const inactive = tokens.filter(t => !t.isActive && !t.isExpired && !t.isRevoked);
398
+ const expired = tokens.filter(t => t.isExpired);
399
+ const revoked = tokens.filter(t => t.isRevoked);
400
+ const depleted = tokens.filter(t => !t.isExpired && !t.isRevoked && t.remaining <= 0);
401
+
402
+ res.json({
403
+ success: true,
404
+ tokens: {
405
+ active,
406
+ inactive,
407
+ expired,
408
+ revoked,
409
+ depleted
410
+ },
411
+ total: tokens.length
412
+ });
413
+ } catch (error) {
414
+ const message = getErrorMessage(error);
415
+ res.status(400).json({ error: message });
416
+ }
417
+ });
418
+
419
+ // POST /actions/tokens/revoke - Revoke a token (admin or agent with own token)
420
+ router.post('/tokens/revoke', requireWalletAuth, async (req: Request, res: Response) => {
421
+ try {
422
+ const { tokenHash } = req.body;
423
+ const auth = req.auth!;
424
+
425
+ // Agent can only revoke their own token
426
+ if (!isAdmin(auth)) {
427
+ if (tokenHash && tokenHash !== auth.tokenHash) {
428
+ res.status(403).json({ error: 'Agents can only revoke their own token' });
429
+ return;
430
+ }
431
+ const success = await revokeToken(auth.tokenHash);
432
+ if (success) {
433
+ logger.tokenRevoked(auth.tokenHash, auth.token.agentId);
434
+ }
435
+ res.json({ success, message: success ? 'Token revoked' : 'Token not found' });
436
+ return;
437
+ }
438
+
439
+ // Admin revoking any token
440
+ if (!tokenHash || typeof tokenHash !== 'string') {
441
+ res.status(400).json({ error: 'tokenHash is required' });
442
+ return;
443
+ }
444
+
445
+ const success = await revokeToken(tokenHash);
446
+
447
+ if (success) {
448
+ events.tokenRevoked({ tokenHash });
449
+ logger.tokenRevoked(tokenHash, 'admin');
450
+ }
451
+
452
+ res.json({ success, message: success ? 'Token revoked' : 'Token not found' });
453
+ } catch (error) {
454
+ const message = getErrorMessage(error);
455
+ res.status(400).json({ error: message });
456
+ }
457
+ });
458
+
459
+ export default router;