polyrob 0.5.0__py3-none-any.whl

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 (745) hide show
  1. agents/README.md +453 -0
  2. agents/__init__.py +164 -0
  3. agents/base_agent.py +743 -0
  4. agents/personality/character.py +119 -0
  5. agents/personality/character_manager.py +309 -0
  6. agents/personality/characters/rob.character.json +56 -0
  7. agents/personality/characters/trump.character.json +63 -0
  8. agents/personality/persona_render.py +78 -0
  9. agents/personality/persona_resolver.py +115 -0
  10. agents/prompt/__init__.py +58 -0
  11. agents/prompt/base_prompt.py +92 -0
  12. agents/prompt/system.py +417 -0
  13. agents/task/README_PATH_MANAGEMENT.md +141 -0
  14. agents/task/__init__.py +162 -0
  15. agents/task/agent/__init__.py +70 -0
  16. agents/task/agent/agent_state.py +408 -0
  17. agents/task/agent/async_delegation.py +274 -0
  18. agents/task/agent/autonomy_state.py +275 -0
  19. agents/task/agent/conversation.py +75 -0
  20. agents/task/agent/core/__init__.py +6 -0
  21. agents/task/agent/core/aux_metering.py +61 -0
  22. agents/task/agent/core/background_review.py +157 -0
  23. agents/task/agent/core/construction.py +1179 -0
  24. agents/task/agent/core/conversational_exit.py +49 -0
  25. agents/task/agent/core/correspondent_gate.py +227 -0
  26. agents/task/agent/core/curator.py +246 -0
  27. agents/task/agent/core/episodic_digest.py +103 -0
  28. agents/task/agent/core/error_recovery.py +480 -0
  29. agents/task/agent/core/history_io.py +257 -0
  30. agents/task/agent/core/llm_provisioning.py +287 -0
  31. agents/task/agent/core/llm_runner.py +547 -0
  32. agents/task/agent/core/logging_io.py +276 -0
  33. agents/task/agent/core/loop_detection.py +262 -0
  34. agents/task/agent/core/memory_prefetch.py +325 -0
  35. agents/task/agent/core/memory_writer.py +667 -0
  36. agents/task/agent/core/model_introspection.py +82 -0
  37. agents/task/agent/core/model_swap.py +177 -0
  38. agents/task/agent/core/next_action_internal.py +1331 -0
  39. agents/task/agent/core/output_validation.py +303 -0
  40. agents/task/agent/core/project_context.py +272 -0
  41. agents/task/agent/core/resources.py +93 -0
  42. agents/task/agent/core/result_processing.py +389 -0
  43. agents/task/agent/core/run_loop.py +661 -0
  44. agents/task/agent/core/safety_lifecycle.py +228 -0
  45. agents/task/agent/core/secret_guard.py +293 -0
  46. agents/task/agent/core/self_wake.py +280 -0
  47. agents/task/agent/core/session_metadata.py +104 -0
  48. agents/task/agent/core/step.py +781 -0
  49. agents/task/agent/core/step_execution.py +398 -0
  50. agents/task/agent/core/step_telemetry.py +222 -0
  51. agents/task/agent/core/test_aux_metering.py +50 -0
  52. agents/task/agent/core/test_billing_failover.py +68 -0
  53. agents/task/agent/core/test_fatal_error_classifier.py +38 -0
  54. agents/task/agent/core/test_judge_validation.py +275 -0
  55. agents/task/agent/core/test_model_swap.py +190 -0
  56. agents/task/agent/core/test_reflection_offload.py +54 -0
  57. agents/task/agent/core/turn_input.py +8 -0
  58. agents/task/agent/core/untrusted_wrap.py +103 -0
  59. agents/task/agent/core/user_ingress.py +228 -0
  60. agents/task/agent/hitl_manager.py +396 -0
  61. agents/task/agent/log_sanitize.py +36 -0
  62. agents/task/agent/message_manager/config.py +97 -0
  63. agents/task/agent/message_manager/service.py +758 -0
  64. agents/task/agent/message_manager/tests.py +305 -0
  65. agents/task/agent/message_manager/tool_call_builder.py +539 -0
  66. agents/task/agent/message_manager/tool_message_repair.py +364 -0
  67. agents/task/agent/message_manager/views.py +143 -0
  68. agents/task/agent/messages/__init__.py +8 -0
  69. agents/task/agent/messages/builders.py +309 -0
  70. agents/task/agent/messages/compactor.py +611 -0
  71. agents/task/agent/messages/context_references.py +271 -0
  72. agents/task/agent/messages/filters.py +333 -0
  73. agents/task/agent/messages/guidance.py +328 -0
  74. agents/task/agent/messages/persistence.py +448 -0
  75. agents/task/agent/messages/retrieval.py +506 -0
  76. agents/task/agent/messages/sqlite_persistence.py +85 -0
  77. agents/task/agent/messages/test_compaction_upgrade.py +349 -0
  78. agents/task/agent/messages/test_persistence_subdir_fix.py +28 -0
  79. agents/task/agent/messages/test_sqlite_persistence.py +54 -0
  80. agents/task/agent/messages/token_counter.py +532 -0
  81. agents/task/agent/orchestrator.py +776 -0
  82. agents/task/agent/profile_manager.py +105 -0
  83. agents/task/agent/profile_registry.py +173 -0
  84. agents/task/agent/prompts.py +1090 -0
  85. agents/task/agent/scenario_registry.py +226 -0
  86. agents/task/agent/service.py +509 -0
  87. agents/task/agent/session.py +984 -0
  88. agents/task/agent/skill_discovery.py +110 -0
  89. agents/task/agent/skill_frontmatter.py +94 -0
  90. agents/task/agent/skill_manager.py +1201 -0
  91. agents/task/agent/skill_store.py +627 -0
  92. agents/task/agent/skill_validation.py +56 -0
  93. agents/task/agent/skill_writer.py +492 -0
  94. agents/task/agent/sub_agent_manager.py +1137 -0
  95. agents/task/agent/test_session_disk_recovery.py +34 -0
  96. agents/task/agent/tests.py +513 -0
  97. agents/task/agent/tool_call_tracker.py +511 -0
  98. agents/task/agent/views.py +469 -0
  99. agents/task/config.py +432 -0
  100. agents/task/constants.py +1384 -0
  101. agents/task/flag_defaults.py +63 -0
  102. agents/task/goals/__init__.py +1 -0
  103. agents/task/goals/autonomy_marker.py +28 -0
  104. agents/task/goals/board.py +690 -0
  105. agents/task/goals/completion_judge.py +247 -0
  106. agents/task/goals/context.py +62 -0
  107. agents/task/goals/dispatcher.py +848 -0
  108. agents/task/goals/escalation.py +102 -0
  109. agents/task/goals/planner.py +114 -0
  110. agents/task/logging_config.py +155 -0
  111. agents/task/path.py +991 -0
  112. agents/task/robust_parse_config.py +400 -0
  113. agents/task/runtime/__init__.py +0 -0
  114. agents/task/runtime/run_as_session.py +130 -0
  115. agents/task/runtime_safety.py +308 -0
  116. agents/task/session/__init__.py +5 -0
  117. agents/task/session/browser_pool.py +130 -0
  118. agents/task/session/cleanup.py +612 -0
  119. agents/task/session/execution.py +397 -0
  120. agents/task/session/feed.py +200 -0
  121. agents/task/session/hitl_ingress.py +201 -0
  122. agents/task/session/hooks.py +83 -0
  123. agents/task/session/multi_agent.py +243 -0
  124. agents/task/session/workspace.py +91 -0
  125. agents/task/session_registry.py +91 -0
  126. agents/task/session_route.py +35 -0
  127. agents/task/sqlite_session_registry.py +261 -0
  128. agents/task/surface_config.py +185 -0
  129. agents/task/telemetry/__init__.py +58 -0
  130. agents/task/telemetry/event_log.py +180 -0
  131. agents/task/telemetry/formatters.py +642 -0
  132. agents/task/telemetry/manager.py +571 -0
  133. agents/task/telemetry/memory_events.py +66 -0
  134. agents/task/telemetry/self_events.py +53 -0
  135. agents/task/telemetry/sequence.py +160 -0
  136. agents/task/telemetry/service.py +1265 -0
  137. agents/task/telemetry/views.py +882 -0
  138. agents/task/templates.py +98 -0
  139. agents/task/test_aux_chain.py +151 -0
  140. agents/task/test_aux_router.py +41 -0
  141. agents/task/tests/test_parse_robustness.py +170 -0
  142. agents/task/tool_defaults.py +110 -0
  143. agents/task/utils.py +762 -0
  144. agents/task/utils_json.py +768 -0
  145. agents/task/utils_webview.py +86 -0
  146. agents/task/workspace_context.py +229 -0
  147. agents/task_agent_lite.py +2398 -0
  148. api/README.md +683 -0
  149. api/TASK_API_DOCS.md +151 -0
  150. api/__init__.py +0 -0
  151. api/a2a/__init__.py +37 -0
  152. api/a2a/agent_card.py +337 -0
  153. api/a2a/client.py +657 -0
  154. api/a2a/endpoints.py +375 -0
  155. api/a2a/models.py +268 -0
  156. api/a2a/streaming.py +419 -0
  157. api/a2a/task_handler.py +929 -0
  158. api/admin_endpoints.py +1584 -0
  159. api/app.py +923 -0
  160. api/auth_constants.py +28 -0
  161. api/auth_endpoints.py +402 -0
  162. api/auth_state.py +36 -0
  163. api/chat_via_task.py +42 -0
  164. api/conversation_manager.py +231 -0
  165. api/dependencies.py +186 -0
  166. api/eip8004_endpoints.py +452 -0
  167. api/hyperliquid_models.py +174 -0
  168. api/hyperliquid_routes.py +450 -0
  169. api/interfaces.py +207 -0
  170. api/jwt_middleware.py +83 -0
  171. api/kb/__init__.py +1 -0
  172. api/kb/endpoints.py +314 -0
  173. api/mcp_models.py +279 -0
  174. api/mcp_routes.py +438 -0
  175. api/middleware.py +526 -0
  176. api/models.py +176 -0
  177. api/openai_compat/__init__.py +0 -0
  178. api/openai_compat/model_map.py +25 -0
  179. api/openai_compat/models.py +59 -0
  180. api/openai_compat/router.py +140 -0
  181. api/payment_endpoints.py +444 -0
  182. api/payment_verification.py +138 -0
  183. api/polymarket_models.py +155 -0
  184. api/polymarket_routes.py +501 -0
  185. api/pricing_endpoints.py +89 -0
  186. api/session_routing.py +54 -0
  187. api/skill_endpoints.py +453 -0
  188. api/task_http_api.py +1856 -0
  189. api/webhooks.py +52 -0
  190. api/x402_endpoints.py +280 -0
  191. avatar/README.md +44 -0
  192. avatar/__init__.py +1 -0
  193. avatar/config/rob.json +7 -0
  194. avatar/mindprint.js +395 -0
  195. avatar/renders/rob.meta.json +20 -0
  196. avatar/renders/rob.png +0 -0
  197. avatar/studio.html +373 -0
  198. avatar/webview/avatar-live.js +44 -0
  199. cli/README.md +89 -0
  200. cli/__init__.py +0 -0
  201. cli/cli_surface.py +67 -0
  202. cli/commands/__init__.py +0 -0
  203. cli/commands/_bootstrap.py +49 -0
  204. cli/commands/_errors.py +53 -0
  205. cli/commands/chat.py +973 -0
  206. cli/commands/config.py +100 -0
  207. cli/commands/dashboard.py +103 -0
  208. cli/commands/doctor.py +222 -0
  209. cli/commands/email.py +174 -0
  210. cli/commands/gateway.py +346 -0
  211. cli/commands/goals.py +509 -0
  212. cli/commands/init.py +276 -0
  213. cli/commands/journey.py +30 -0
  214. cli/commands/kb.py +280 -0
  215. cli/commands/model.py +107 -0
  216. cli/commands/owner.py +431 -0
  217. cli/commands/pfp.py +232 -0
  218. cli/commands/run.py +414 -0
  219. cli/commands/serve.py +41 -0
  220. cli/commands/session.py +684 -0
  221. cli/commands/skill_install.py +786 -0
  222. cli/commands/skills.py +128 -0
  223. cli/commands/subagents.py +100 -0
  224. cli/commands/surface.py +81 -0
  225. cli/commands/telegram.py +185 -0
  226. cli/commands/todos.py +209 -0
  227. cli/commands/tools.py +102 -0
  228. cli/commands/update.py +316 -0
  229. cli/commands/whatsapp.py +175 -0
  230. cli/config_store.py +276 -0
  231. cli/gitignore.py +43 -0
  232. cli/inventory.py +46 -0
  233. cli/keys.py +101 -0
  234. cli/persona.py +17 -0
  235. cli/polyrob.py +137 -0
  236. cli/ui/__init__.py +64 -0
  237. cli/ui/activity.py +141 -0
  238. cli/ui/app.py +538 -0
  239. cli/ui/banner.py +233 -0
  240. cli/ui/blocks.py +534 -0
  241. cli/ui/bootstrap_notice.py +47 -0
  242. cli/ui/commands/__init__.py +41 -0
  243. cli/ui/commands/h_cron.py +64 -0
  244. cli/ui/commands/h_journey.py +164 -0
  245. cli/ui/commands/h_kb.py +121 -0
  246. cli/ui/commands/h_learn.py +100 -0
  247. cli/ui/commands/h_mcp.py +215 -0
  248. cli/ui/commands/h_self.py +113 -0
  249. cli/ui/commands/h_skills.py +314 -0
  250. cli/ui/commands/handlers.py +1672 -0
  251. cli/ui/commands/registry.py +426 -0
  252. cli/ui/dialog.py +425 -0
  253. cli/ui/event_registry.py +110 -0
  254. cli/ui/event_specs.py +45 -0
  255. cli/ui/events.py +433 -0
  256. cli/ui/identity.py +15 -0
  257. cli/ui/lifecycle.py +159 -0
  258. cli/ui/live_hooks.py +43 -0
  259. cli/ui/model_selector.py +532 -0
  260. cli/ui/persistent_loop.py +181 -0
  261. cli/ui/pick.py +113 -0
  262. cli/ui/plain_renderer.py +351 -0
  263. cli/ui/renderer.py +384 -0
  264. cli/ui/rich_renderer.py +533 -0
  265. cli/ui/secrets.py +115 -0
  266. cli/ui/state.py +376 -0
  267. cli/ui/statusbar.py +231 -0
  268. cli/ui/streaming.py +254 -0
  269. cli/ui/terminal_render.py +105 -0
  270. cli/ui/theme.py +132 -0
  271. cli/update/__init__.py +6 -0
  272. cli/update/context.py +121 -0
  273. cli/update/detect.py +176 -0
  274. cli/update/engine.py +95 -0
  275. cli/update/migrate_guarded.py +55 -0
  276. cli/update/process_guard.py +215 -0
  277. cli/update/runners.py +78 -0
  278. cli/update/snapshot.py +270 -0
  279. cli/update/versions.py +205 -0
  280. core/README.md +377 -0
  281. core/__init__.py +174 -0
  282. core/assets.py +87 -0
  283. core/async_bridge.py +76 -0
  284. core/autonomy_runtime.py +259 -0
  285. core/base_component.py +302 -0
  286. core/bootstrap.py +681 -0
  287. core/bot.py +310 -0
  288. core/config.py +833 -0
  289. core/constants.py +126 -0
  290. core/container.py +462 -0
  291. core/db_manifest.py +101 -0
  292. core/embedding.py +57 -0
  293. core/env.py +39 -0
  294. core/exceptions.py +287 -0
  295. core/flags.py +144 -0
  296. core/flags_catalog.py +345 -0
  297. core/home_migration.py +62 -0
  298. core/identity.py +118 -0
  299. core/initialization.py +1004 -0
  300. core/instance.py +537 -0
  301. core/interactive_gate.py +96 -0
  302. core/logging.py +436 -0
  303. core/owner_doc_writer.py +176 -0
  304. core/pairing.py +193 -0
  305. core/path_safety.py +20 -0
  306. core/paths.py +27 -0
  307. core/payment_config.py +21 -0
  308. core/permissions.py +322 -0
  309. core/runtime_config.py +96 -0
  310. core/runtime_paths.py +135 -0
  311. core/seams.py +35 -0
  312. core/secret_scan.py +25 -0
  313. core/secret_scrub.py +71 -0
  314. core/secrets.py +35 -0
  315. core/security_logging_filter.py +148 -0
  316. core/self_context_writer.py +333 -0
  317. core/self_evolution.py +294 -0
  318. core/session_context.py +47 -0
  319. core/sqlite_util.py +59 -0
  320. core/surfaces/__init__.py +16 -0
  321. core/surfaces/access.py +118 -0
  322. core/surfaces/binding.py +88 -0
  323. core/surfaces/bootstrap.py +78 -0
  324. core/surfaces/circuit.py +168 -0
  325. core/surfaces/continuity.py +77 -0
  326. core/surfaces/correspondents.py +228 -0
  327. core/surfaces/dispatcher.py +216 -0
  328. core/surfaces/envelopes.py +75 -0
  329. core/surfaces/gc.py +30 -0
  330. core/surfaces/idempotency.py +57 -0
  331. core/surfaces/inbound_webhook.py +124 -0
  332. core/surfaces/media.py +32 -0
  333. core/surfaces/message_router.py +91 -0
  334. core/surfaces/outbound_allowlist.py +80 -0
  335. core/surfaces/outbound_dispatcher.py +110 -0
  336. core/surfaces/outbound_mirror.py +37 -0
  337. core/surfaces/outbound_queue.py +107 -0
  338. core/surfaces/outbound_target.py +12 -0
  339. core/surfaces/owner_admin.py +29 -0
  340. core/surfaces/proactive.py +78 -0
  341. core/surfaces/progress.py +108 -0
  342. core/surfaces/rate_bucket.py +21 -0
  343. core/surfaces/registry.py +51 -0
  344. core/surfaces/rendering.py +53 -0
  345. core/surfaces/send_policy.py +14 -0
  346. core/surfaces/serialize.py +24 -0
  347. core/surfaces/session_chat_registry.py +112 -0
  348. core/surfaces/session_policy.py +60 -0
  349. core/surfaces/surface.py +288 -0
  350. core/surfaces/transcription.py +88 -0
  351. core/surfaces/voice_echo.py +26 -0
  352. core/surfaces/voice_guard.py +23 -0
  353. core/tickers.py +174 -0
  354. core/tool_catalog.py +192 -0
  355. core/version.py +75 -0
  356. core/wallet/__init__.py +5 -0
  357. core/wallet/agent_wallet.py +59 -0
  358. core/wallet/audit_sink.py +61 -0
  359. core/wallet/config.py +70 -0
  360. core/wallet/factory.py +87 -0
  361. core/wallet/policy.py +104 -0
  362. core/wallet/signer.py +51 -0
  363. cron/README.md +65 -0
  364. cron/__init__.py +0 -0
  365. cron/delivery.py +277 -0
  366. cron/digest.py +124 -0
  367. cron/jobs.py +207 -0
  368. cron/runner.py +297 -0
  369. cron/schedule.py +194 -0
  370. cron/scheduler.py +201 -0
  371. cron/service.py +57 -0
  372. cron/wake_gate.py +223 -0
  373. data/__init__.py +1 -0
  374. data/prompts/autov2_prompts.json +87 -0
  375. data/prompts/skills/browser-automation/SKILL.md +43 -0
  376. data/prompts/skills/coding-workflow/SKILL.md +36 -0
  377. data/prompts/skills/crypto-trading-safety/SKILL.md +53 -0
  378. data/prompts/skills/document-writing/SKILL.md +33 -0
  379. data/prompts/skills/email-comms/SKILL.md +33 -0
  380. data/prompts/skills/file-data-ops/SKILL.md +35 -0
  381. data/prompts/skills/hyperliquid-account-review/SKILL.md +48 -0
  382. data/prompts/skills/hyperliquid-market-data/SKILL.md +52 -0
  383. data/prompts/skills/hyperliquid-trading/SKILL.md +59 -0
  384. data/prompts/skills/lead-research/SKILL.md +86 -0
  385. data/prompts/skills/market-research-brief/SKILL.md +104 -0
  386. data/prompts/skills/person-analyzer/SKILL.md +124 -0
  387. data/prompts/skills/polymarket-market-research/SKILL.md +54 -0
  388. data/prompts/skills/polymarket-portfolio-review/SKILL.md +50 -0
  389. data/prompts/skills/polymarket-trading/SKILL.md +55 -0
  390. data/prompts/skills/presentation-creator/SKILL.md +36 -0
  391. data/prompts/skills/project-analyzer/SKILL.md +169 -0
  392. data/prompts/skills/rules.json +268 -0
  393. data/prompts/skills/secret-handling/SKILL.md +20 -0
  394. data/prompts/skills/skill-authoring/SKILL.md +31 -0
  395. data/prompts/skills/skill-security-review/SKILL.md +27 -0
  396. data/prompts/skills/social-discovery/SKILL.md +57 -0
  397. data/prompts/skills/task-planning/SKILL.md +33 -0
  398. data/prompts/skills/web-research/SKILL.md +39 -0
  399. data/prompts/skills/web-scraping/SKILL.md +73 -0
  400. data/prompts/skills/x-engagement/SKILL.md +69 -0
  401. data/prompts/system_prompts.json +4 -0
  402. data/subscription_channels.json +5 -0
  403. modules/README.md +503 -0
  404. modules/__init__.py +124 -0
  405. modules/auth/__init__.py +13 -0
  406. modules/auth/api_key_manager.py +157 -0
  407. modules/auth/identity_mapper.py +380 -0
  408. modules/auth/siwe_auth.py +273 -0
  409. modules/auth/tier_manager.py +173 -0
  410. modules/base_module.py +190 -0
  411. modules/credits/__init__.py +29 -0
  412. modules/credits/balance_manager.py +237 -0
  413. modules/credits/cost_utils.py +129 -0
  414. modules/credits/pricing.py +139 -0
  415. modules/credits/unified_ledger.py +148 -0
  416. modules/credits/usage_meter.py +310 -0
  417. modules/credits/usage_tracker.py +674 -0
  418. modules/database/__init__.py +33 -0
  419. modules/database/audit_log.py +322 -0
  420. modules/database/auth_tables.py +276 -0
  421. modules/database/connection.py +644 -0
  422. modules/database/connection_pool.py +305 -0
  423. modules/database/conversation_contexts.py +446 -0
  424. modules/database/database_manager.py +237 -0
  425. modules/database/hyperliquid.py +375 -0
  426. modules/database/polymarket.py +574 -0
  427. modules/database/user_mcp_servers.py +728 -0
  428. modules/database/user_profiles.py +562 -0
  429. modules/database/utils.py +107 -0
  430. modules/database/x402_tables.py +92 -0
  431. modules/eip8004/README.md +1069 -0
  432. modules/eip8004/__init__.py +65 -0
  433. modules/eip8004/contracts.py +498 -0
  434. modules/eip8004/models.py +271 -0
  435. modules/eip8004/registration.py +148 -0
  436. modules/eip8004/reputation.py +367 -0
  437. modules/eip8004/validation.py +277 -0
  438. modules/llm/__init__.py +97 -0
  439. modules/llm/adapters.py +963 -0
  440. modules/llm/anthropic_client.py +831 -0
  441. modules/llm/available_models.py +106 -0
  442. modules/llm/brain_scrubber.py +137 -0
  443. modules/llm/cache_hints.py +162 -0
  444. modules/llm/deepseek_client.py +562 -0
  445. modules/llm/gemini_client.py +1367 -0
  446. modules/llm/llm_client.py +1139 -0
  447. modules/llm/llm_client_registry.py +188 -0
  448. modules/llm/llm_factory.py +237 -0
  449. modules/llm/llm_manager.py +1036 -0
  450. modules/llm/messages.py +374 -0
  451. modules/llm/model_registry.py +1935 -0
  452. modules/llm/nvidia_client.py +125 -0
  453. modules/llm/openai_client.py +800 -0
  454. modules/llm/openrouter_client.py +969 -0
  455. modules/llm/profiles.py +178 -0
  456. modules/llm/test_anthropic_cache_capture.py +35 -0
  457. modules/llm/test_cached_pricing_derivation.py +24 -0
  458. modules/llm/think_scrubber.py +444 -0
  459. modules/llm/token_counter.py +268 -0
  460. modules/memory/__init__.py +35 -0
  461. modules/memory/backend_factory.py +83 -0
  462. modules/memory/cache_manager.py +186 -0
  463. modules/memory/episodic.py +108 -0
  464. modules/memory/local_vector_memory_provider.py +552 -0
  465. modules/memory/memory_manager.py +225 -0
  466. modules/memory/models.py +312 -0
  467. modules/memory/provider.py +210 -0
  468. modules/memory/registry.py +205 -0
  469. modules/memory/sqlite_memory_provider.py +767 -0
  470. modules/memory/task/__init__.py +52 -0
  471. modules/memory/task/compaction_manager.py +157 -0
  472. modules/memory/task/context_retriever.py +809 -0
  473. modules/memory/task/hierarchical_memory.py +1002 -0
  474. modules/memory/task/lexical_retriever.py +71 -0
  475. modules/memory/task/null_context_manager.py +213 -0
  476. modules/memory/task/phase_manager.py +625 -0
  477. modules/memory/task/reflection_service.py +157 -0
  478. modules/memory/task/semantic_retriever.py +218 -0
  479. modules/memory/task/task_context_manager.py +1165 -0
  480. modules/memory/task/test_forgetting_engages.py +24 -0
  481. modules/memory/task/test_reflection_llm.py +39 -0
  482. modules/memory/task/test_threat_scan.py +53 -0
  483. modules/memory/task/threat_scan.py +90 -0
  484. modules/memory/test_sqlite_memory_provider.py +106 -0
  485. modules/memory/user_profile_manager.py +527 -0
  486. modules/payments/__init__.py +11 -0
  487. modules/payments/deposit_monitor.py +478 -0
  488. modules/payments/price_oracle.py +70 -0
  489. modules/payments/treasury_sweeper.py +401 -0
  490. modules/payments/wallet_generator.py +145 -0
  491. modules/pfp/__init__.py +1 -0
  492. modules/pfp/config.py +92 -0
  493. modules/pfp/mesh.py +565 -0
  494. modules/pfp/push.py +69 -0
  495. modules/pfp/renderer.py +85 -0
  496. modules/pfp/store.py +82 -0
  497. modules/skills/__init__.py +1 -0
  498. modules/skills/skill_usage.py +248 -0
  499. modules/transcription/__init__.py +24 -0
  500. modules/transcription/base.py +20 -0
  501. modules/transcription/faster_whisper_transcriber.py +55 -0
  502. modules/x402/README.md +249 -0
  503. modules/x402/__init__.py +22 -0
  504. modules/x402/invoicing.py +416 -0
  505. modules/x402/middleware.py +364 -0
  506. modules/x402/settlement_watcher.py +145 -0
  507. modules/x402/x402_integration.py +344 -0
  508. polyrob-0.5.0.dist-info/METADATA +482 -0
  509. polyrob-0.5.0.dist-info/RECORD +745 -0
  510. polyrob-0.5.0.dist-info/WHEEL +5 -0
  511. polyrob-0.5.0.dist-info/entry_points.txt +2 -0
  512. polyrob-0.5.0.dist-info/licenses/LICENSE +21 -0
  513. polyrob-0.5.0.dist-info/top_level.txt +12 -0
  514. surfaces/README.md +70 -0
  515. surfaces/__init__.py +0 -0
  516. surfaces/email/__init__.py +0 -0
  517. surfaces/email/dedup.py +44 -0
  518. surfaces/email/harness.py +187 -0
  519. surfaces/email/inbound.py +159 -0
  520. surfaces/email/seed.py +60 -0
  521. surfaces/email/surface.py +105 -0
  522. surfaces/telegram/__init__.py +0 -0
  523. surfaces/telegram/dedup.py +83 -0
  524. surfaces/telegram/harness.py +886 -0
  525. surfaces/telegram/inbound.py +145 -0
  526. surfaces/telegram/interactive_tools.py +49 -0
  527. surfaces/telegram/markdown.py +347 -0
  528. surfaces/telegram/rate_limit.py +167 -0
  529. surfaces/telegram/surface.py +187 -0
  530. surfaces/telegram/voice.py +52 -0
  531. surfaces/whatsapp/__init__.py +0 -0
  532. surfaces/whatsapp/client.py +51 -0
  533. surfaces/whatsapp/harness.py +62 -0
  534. surfaces/whatsapp/inbound.py +132 -0
  535. surfaces/whatsapp/surface.py +64 -0
  536. surfaces/whatsapp/window.py +34 -0
  537. tools/README.md +578 -0
  538. tools/__init__.py +424 -0
  539. tools/alchemy/__init__.py +5 -0
  540. tools/alchemy/alchemy_tool.py +389 -0
  541. tools/anysite/__init__.py +17 -0
  542. tools/anysite/client.py +88 -0
  543. tools/anysite/tool.py +89 -0
  544. tools/base_tool.py +468 -0
  545. tools/browser/__init__.py +22 -0
  546. tools/browser/actions.py +118 -0
  547. tools/browser/browser.py +1457 -0
  548. tools/browser/browser_manager.py +690 -0
  549. tools/browser/context.py +1748 -0
  550. tools/browser/playwright_utils.py +137 -0
  551. tools/browser/views.py +56 -0
  552. tools/code_exec/SANDBOX_SECURITY.md +144 -0
  553. tools/code_exec/__init__.py +178 -0
  554. tools/code_exec/backend.py +73 -0
  555. tools/code_exec/backends/__init__.py +1 -0
  556. tools/code_exec/backends/docker.py +779 -0
  557. tools/code_exec/backends/local_subprocess.py +154 -0
  558. tools/code_exec/env_policy.py +45 -0
  559. tools/code_exec/result.py +42 -0
  560. tools/code_exec/sandbox_guard.py +59 -0
  561. tools/code_exec/tool.py +227 -0
  562. tools/coding/__init__.py +50 -0
  563. tools/coding/edit.py +107 -0
  564. tools/coding/search.py +113 -0
  565. tools/coding/tool.py +353 -0
  566. tools/collabland/__init__.py +5 -0
  567. tools/collabland/collabland_tool.py +471 -0
  568. tools/controller/__init__.py +0 -0
  569. tools/controller/_helpers.py +165 -0
  570. tools/controller/action_registration.py +1765 -0
  571. tools/controller/approval.py +244 -0
  572. tools/controller/approval_interactive.py +90 -0
  573. tools/controller/delegation.py +195 -0
  574. tools/controller/execution.py +797 -0
  575. tools/controller/execution_context.py +141 -0
  576. tools/controller/hooks.py +135 -0
  577. tools/controller/introspection.py +344 -0
  578. tools/controller/mcp_registrar.py +190 -0
  579. tools/controller/message_send.py +31 -0
  580. tools/controller/registry/__init__.py +4 -0
  581. tools/controller/registry/schema_generators.py +395 -0
  582. tools/controller/registry/schema_sanitizer.py +406 -0
  583. tools/controller/registry/service.py +1266 -0
  584. tools/controller/registry/views.py +151 -0
  585. tools/controller/service.py +409 -0
  586. tools/controller/tool_management.py +334 -0
  587. tools/controller/types.py +28 -0
  588. tools/controller/views.py +450 -0
  589. tools/cronjob_tools.py +166 -0
  590. tools/crypto_trade_gate.py +59 -0
  591. tools/descriptors.py +452 -0
  592. tools/dom/__init__.py +0 -0
  593. tools/dom/history_tree_processor/service.py +244 -0
  594. tools/dom/history_tree_processor/view.py +99 -0
  595. tools/dom/service.py +471 -0
  596. tools/dom/views.py +346 -0
  597. tools/email_tool.py +403 -0
  598. tools/exceptions.py +169 -0
  599. tools/filesystem.py +1038 -0
  600. tools/filesystem_docproc.py +331 -0
  601. tools/filesystem_pdf.py +755 -0
  602. tools/git/__init__.py +42 -0
  603. tools/git/tool.py +258 -0
  604. tools/github/__init__.py +33 -0
  605. tools/github/client.py +94 -0
  606. tools/github/tool.py +237 -0
  607. tools/goal_tools.py +309 -0
  608. tools/hyperliquid/__init__.py +56 -0
  609. tools/hyperliquid/models.py +233 -0
  610. tools/hyperliquid/service.py +1680 -0
  611. tools/knowledge_ingest.py +950 -0
  612. tools/mcp/README.md +535 -0
  613. tools/mcp/__init__.py +52 -0
  614. tools/mcp/catalog.py +115 -0
  615. tools/mcp/config.py +337 -0
  616. tools/mcp/mcp_tool.py +1415 -0
  617. tools/mcp/param_coercion.py +289 -0
  618. tools/mcp/protocol.py +1520 -0
  619. tools/mcp/rate_limit.py +50 -0
  620. tools/mcp/security.py +424 -0
  621. tools/mcp/self_install.py +111 -0
  622. tools/mcp/server_manager.py +829 -0
  623. tools/mcp/subscriptions.py +72 -0
  624. tools/mcp/user_mcp_service.py +899 -0
  625. tools/mcp/validation_tracker.py +89 -0
  626. tools/mcp/views.py +346 -0
  627. tools/oauth/__init__.py +17 -0
  628. tools/oauth/manager.py +75 -0
  629. tools/oauth/provider.py +69 -0
  630. tools/oauth/providers/__init__.py +1 -0
  631. tools/oauth/providers/generic_oauth2.py +96 -0
  632. tools/perplexity_tool.py +280 -0
  633. tools/polymarket/__init__.py +41 -0
  634. tools/polymarket/clob_adapter.py +76 -0
  635. tools/polymarket/models.py +182 -0
  636. tools/polymarket/service.py +2082 -0
  637. tools/self_env/__init__.py +61 -0
  638. tools/self_env/tool.py +274 -0
  639. tools/shell/__init__.py +80 -0
  640. tools/shell/backend_pool.py +118 -0
  641. tools/shell/discipline.py +62 -0
  642. tools/shell/executor.py +157 -0
  643. tools/shell/loopback_allow.py +87 -0
  644. tools/shell/process_registry.py +113 -0
  645. tools/shell/process_tool.py +131 -0
  646. tools/shell/state.py +129 -0
  647. tools/shell/tool.py +151 -0
  648. tools/task_tool.py +637 -0
  649. tools/twitter_tool.py +2086 -0
  650. tools/user_directory.py +179 -0
  651. tools/web_fetch/__init__.py +3 -0
  652. tools/web_fetch/fetcher.py +144 -0
  653. tools/web_fetch/render.py +44 -0
  654. tools/web_fetch/tool.py +60 -0
  655. tools/x402/__init__.py +60 -0
  656. tools/x402/client.py +47 -0
  657. tools/x402/invoice_tool.py +143 -0
  658. tools/x402/real_client.py +250 -0
  659. tools/x402/service.py +131 -0
  660. utils/README.md +498 -0
  661. utils/__init__.py +46 -0
  662. utils/auth_utils.py +114 -0
  663. utils/bounded_collections.py +162 -0
  664. utils/circuit_breaker.py +389 -0
  665. utils/gif_utils.py +435 -0
  666. utils/markdown_utils.py +291 -0
  667. utils/message_utils.py +147 -0
  668. utils/metrics.py +36 -0
  669. utils/path_validator.py +227 -0
  670. utils/rate_limit_manager.py +399 -0
  671. utils/result_size.py +296 -0
  672. utils/time_utils.py +206 -0
  673. utils/user_utils.py +188 -0
  674. webview/README.md +111 -0
  675. webview/RENAME_AND_ALIGN_HANDOFF.md +200 -0
  676. webview/__init__.py +8 -0
  677. webview/activity.py +636 -0
  678. webview/owner_auth.py +108 -0
  679. webview/pages.py +448 -0
  680. webview/repair_sessions.py +399 -0
  681. webview/server.py +4323 -0
  682. webview/server_launcher.py +112 -0
  683. webview/static/css/activity.css +89 -0
  684. webview/static/css/chat.css +1772 -0
  685. webview/static/css/components.css +1190 -0
  686. webview/static/css/config-panel.css +651 -0
  687. webview/static/css/pages/admin.css +766 -0
  688. webview/static/css/pages/profile.css +231 -0
  689. webview/static/css/pages/settings.css +297 -0
  690. webview/static/css/pages/signin.css +316 -0
  691. webview/static/css/style.css +4250 -0
  692. webview/static/css/variables.css +147 -0
  693. webview/static/css/workspace-fullwidth-fix.css +109 -0
  694. webview/static/img/favicon.ico +0 -0
  695. webview/static/js/activity.js +394 -0
  696. webview/static/js/admin/activity.js +186 -0
  697. webview/static/js/admin/dashboard.js +163 -0
  698. webview/static/js/admin/user_detail.js +400 -0
  699. webview/static/js/admin/users.js +174 -0
  700. webview/static/js/admin/utils.js +214 -0
  701. webview/static/js/chat.js +3178 -0
  702. webview/static/js/config-panel.js +522 -0
  703. webview/static/js/constants.js +20 -0
  704. webview/static/js/error-handler.js +226 -0
  705. webview/static/js/ethers.min.js +1 -0
  706. webview/static/js/event-filter.js +137 -0
  707. webview/static/js/event-store.js +351 -0
  708. webview/static/js/file-attachments.js +374 -0
  709. webview/static/js/file-loader.js +163 -0
  710. webview/static/js/index.js +103 -0
  711. webview/static/js/performance-utils.js +196 -0
  712. webview/static/js/profile.js +396 -0
  713. webview/static/js/screenshot.js +386 -0
  714. webview/static/js/session.js +1527 -0
  715. webview/static/js/settings.js +1288 -0
  716. webview/static/js/sidebar-data.js +766 -0
  717. webview/static/js/sidebar-toggle.js +296 -0
  718. webview/static/js/socket.io.min.js +7 -0
  719. webview/static/js/stats.js +355 -0
  720. webview/static/js/ui-utils.js +420 -0
  721. webview/static/js/workspace.js +966 -0
  722. webview/stats_service.py +613 -0
  723. webview/templates/__init__.py +1 -0
  724. webview/templates/__pycache__/__init__.cpython-311.pyc +0 -0
  725. webview/templates/activity.html +36 -0
  726. webview/templates/admin/activity.html +122 -0
  727. webview/templates/admin/dashboard.html +112 -0
  728. webview/templates/admin/user_detail.html +228 -0
  729. webview/templates/admin/users.html +105 -0
  730. webview/templates/autonomy.html +60 -0
  731. webview/templates/error.html +133 -0
  732. webview/templates/finance.html +90 -0
  733. webview/templates/identity.html +73 -0
  734. webview/templates/index.html +42 -0
  735. webview/templates/layout.html +125 -0
  736. webview/templates/memory.html +59 -0
  737. webview/templates/owner_login.html +15 -0
  738. webview/templates/profile.html +121 -0
  739. webview/templates/session.html +312 -0
  740. webview/templates/settings.html +534 -0
  741. webview/templates/sidebar.html +112 -0
  742. webview/templates/signin.html +213 -0
  743. webview/templates/status.html +14 -0
  744. webview/templates/system.html +45 -0
  745. webview/webgate.py +196 -0
@@ -0,0 +1,273 @@
1
+ """Sign-In with Ethereum (SIWE) authentication - FREE alternative to Privy.
2
+
3
+ SIWE is the industry standard for wallet authentication:
4
+ - Used by ENS, OpenSea, and many others
5
+ - Completely free, no usage limits
6
+ - Works with any wallet
7
+ - Secure by design with nonce management
8
+ """
9
+
10
+ import logging
11
+ from datetime import datetime, timedelta
12
+ from typing import Optional
13
+ import secrets
14
+ import hashlib
15
+ from eth_account.messages import encode_defunct
16
+ from web3 import Web3
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class SIWEAuthenticator:
22
+ """
23
+ Free wallet authentication using SIWE (Sign-In with Ethereum).
24
+
25
+ This replaces Privy with a completely free solution.
26
+ """
27
+
28
+ def __init__(self, db):
29
+ """
30
+ Initialize SIWE authenticator.
31
+
32
+ Args:
33
+ db: Database manager instance
34
+ """
35
+ self.db = db
36
+ self.w3 = Web3()
37
+ self.logger = logging.getLogger('auth.siwe')
38
+
39
+ async def generate_nonce(self, wallet_address: str, chain_id: int = 1) -> str:
40
+ """
41
+ Generate authentication nonce for wallet.
42
+
43
+ Nonces prevent replay attacks. The nonce is bound to the chain_id it
44
+ was issued for, so a signed message declaring a different chain can
45
+ never be validated against it (see _verify_nonce).
46
+
47
+ Args:
48
+ wallet_address: Ethereum wallet address
49
+ chain_id: Blockchain ID this nonce is being issued for (default: 1 = Ethereum mainnet)
50
+
51
+ Returns:
52
+ Random nonce string
53
+ """
54
+ nonce = secrets.token_hex(32)
55
+
56
+ # Store nonce with expiration (5 minutes)
57
+ await self.db.execute("""
58
+ INSERT OR REPLACE INTO auth_nonces (
59
+ wallet_address, nonce, chain_id, expires_at
60
+ ) VALUES (?, ?, ?, datetime('now', '+5 minutes'))
61
+ """, (wallet_address.lower(), nonce, chain_id))
62
+
63
+ self.logger.debug(f"Generated nonce for {wallet_address[:8]}... (chain {chain_id})")
64
+
65
+ return nonce
66
+
67
+ async def verify_signature(
68
+ self,
69
+ wallet_address: str,
70
+ message: str,
71
+ signature: str,
72
+ nonce: Optional[str] = None
73
+ ) -> bool:
74
+ """
75
+ Verify wallet signature.
76
+
77
+ Args:
78
+ wallet_address: Ethereum wallet address
79
+ message: Signed message
80
+ signature: Signature from wallet
81
+ nonce: Optional nonce to verify (prevents replay attacks)
82
+
83
+ Returns:
84
+ True if signature is valid and nonce is correct
85
+ """
86
+
87
+ try:
88
+ # 1. Verify signature cryptographically
89
+ message_hash = encode_defunct(text=message)
90
+ recovered_address = self.w3.eth.account.recover_message(
91
+ message_hash,
92
+ signature=signature
93
+ )
94
+
95
+ if recovered_address.lower() != wallet_address.lower():
96
+ self.logger.warning(
97
+ f"Signature verification failed: expected {wallet_address[:8]}..., "
98
+ f"got {recovered_address[:8]}..."
99
+ )
100
+ return False
101
+
102
+ # 2. Verify nonce if provided (prevents replay attacks), bound to the
103
+ # chain_id declared in the submitted message (prevents chain-ID replay:
104
+ # a nonce issued for one chain must not validate a message claiming another).
105
+ if nonce:
106
+ submitted_chain_id = self._extract_chain_id(message)
107
+ valid_nonce = await self._verify_nonce(wallet_address, nonce, chain_id=submitted_chain_id)
108
+ if not valid_nonce:
109
+ self.logger.warning(f"Invalid or expired nonce for {wallet_address[:8]}...")
110
+ return False
111
+
112
+ # Consume nonce (one-time use)
113
+ await self._consume_nonce(wallet_address, nonce)
114
+
115
+ # 3. Check message freshness (5 minute window)
116
+ if not self._check_message_freshness(message):
117
+ self.logger.warning(f"Message expired for {wallet_address[:8]}...")
118
+ return False
119
+
120
+ self.logger.info(f"Successfully verified signature for {wallet_address[:8]}...")
121
+ return True
122
+
123
+ except Exception as e:
124
+ self.logger.error(f"Signature verification error: {e}")
125
+ return False
126
+
127
+ async def create_siwe_message(
128
+ self,
129
+ wallet_address: str,
130
+ domain: str,
131
+ uri: str,
132
+ chain_id: int = 1 # Ethereum mainnet by default
133
+ ) -> dict:
134
+ """
135
+ Create SIWE-compliant authentication message.
136
+
137
+ SIWE format: https://eips.ethereum.org/EIPS/eip-4361
138
+
139
+ Args:
140
+ wallet_address: User's wallet address
141
+ domain: Your domain (e.g., "app.your-polyrob-host.example")
142
+ uri: Your app URI
143
+ chain_id: Blockchain ID (1=Ethereum, 137=Polygon, 8453=Base, 42161=Arbitrum)
144
+
145
+ Returns:
146
+ Dict with message and nonce
147
+ """
148
+
149
+ nonce = await self.generate_nonce(wallet_address, chain_id=chain_id)
150
+ issued_at = datetime.utcnow().isoformat() + 'Z'
151
+ expiration = (datetime.utcnow() + timedelta(minutes=5)).isoformat() + 'Z'
152
+
153
+ # SIWE-compliant message format
154
+ message = f"""{domain} wants you to sign in with your Ethereum account:
155
+ {wallet_address}
156
+
157
+ Sign in to POLYROB - AI Automation Platform
158
+
159
+ URI: {uri}
160
+ Version: 1
161
+ Chain ID: {chain_id}
162
+ Nonce: {nonce}
163
+ Issued At: {issued_at}
164
+ Expiration Time: {expiration}"""
165
+
166
+ return {
167
+ "message": message,
168
+ "nonce": nonce,
169
+ "issued_at": issued_at,
170
+ "expiration": expiration
171
+ }
172
+
173
+ async def _verify_nonce(self, wallet_address: str, nonce: str, chain_id: Optional[int] = None) -> bool:
174
+ """Verify nonce is valid, not expired, and — when the stored row is
175
+ bound to a chain — was issued for that same chain.
176
+
177
+ Fail CLOSED, keyed off the STORED row's chain_id (not the submitted
178
+ chain_id): if the row has a concrete chain_id, the submitted message
179
+ MUST declare the matching chain, INCLUDING the case where the
180
+ submitted chain_id is None (the attacker simply omitted the
181
+ `Chain ID:` line, or it failed to parse). Omitting the line is not a
182
+ way to skip the check — it is trivially attacker-controlled request
183
+ data. A legacy row with chain_id IS NULL (pre-migration) still skips
184
+ the check, preserving the grace period; every row written by
185
+ generate_nonce today always carries a concrete chain_id.
186
+ """
187
+
188
+ result = await self.db.fetch_one("""
189
+ SELECT nonce, chain_id FROM auth_nonces
190
+ WHERE wallet_address = ?
191
+ AND nonce = ?
192
+ AND expires_at > datetime('now')
193
+ AND used = 0
194
+ """, (wallet_address.lower(), nonce))
195
+
196
+ if result is None:
197
+ return False
198
+
199
+ if result["chain_id"] is not None and result["chain_id"] != chain_id:
200
+ self.logger.warning(
201
+ f"Chain ID mismatch for {wallet_address[:8]}...: "
202
+ f"issued for {result['chain_id']}, submitted {chain_id}"
203
+ )
204
+ return False
205
+
206
+ return True
207
+
208
+ @staticmethod
209
+ def _extract_chain_id(message: str) -> Optional[int]:
210
+ """Parse the `Chain ID:` line out of a SIWE message body."""
211
+ for line in message.split('\n'):
212
+ if line.startswith('Chain ID:'):
213
+ try:
214
+ return int(line.split('Chain ID:')[1].strip())
215
+ except ValueError:
216
+ return None
217
+ return None
218
+
219
+ async def _consume_nonce(self, wallet_address: str, nonce: str):
220
+ """Mark nonce as used (one-time use)."""
221
+
222
+ await self.db.execute("""
223
+ UPDATE auth_nonces
224
+ SET used = 1
225
+ WHERE wallet_address = ? AND nonce = ?
226
+ """, (wallet_address.lower(), nonce))
227
+
228
+ def _check_message_freshness(self, message: str, max_age: int = 300) -> bool:
229
+ """
230
+ Check if message was created recently.
231
+
232
+ Prevents replay attacks with old signatures.
233
+
234
+ Args:
235
+ message: The signed message
236
+ max_age: Maximum age in seconds (default: 5 minutes)
237
+
238
+ Returns:
239
+ True if message is fresh
240
+ """
241
+
242
+ try:
243
+ # Extract "Issued At" from SIWE message
244
+ for line in message.split('\n'):
245
+ if line.startswith('Issued At:'):
246
+ issued_at_str = line.split('Issued At:')[1].strip()
247
+ issued_at = datetime.fromisoformat(issued_at_str.replace('Z', ''))
248
+ age = (datetime.utcnow() - issued_at).total_seconds()
249
+ return age < max_age
250
+
251
+ # Fallback: check for timestamp in message
252
+ if 'timestamp:' in message.lower():
253
+ timestamp_str = message.split('timestamp:')[-1].strip().split()[0]
254
+ timestamp = int(timestamp_str)
255
+ age = datetime.now().timestamp() - timestamp
256
+ return age < max_age
257
+
258
+ except Exception as e:
259
+ self.logger.debug(f"Could not parse message timestamp: {e}")
260
+
261
+ # If we can't verify freshness, reject (fail-safe)
262
+ return False
263
+
264
+ async def cleanup_expired_nonces(self):
265
+ """Clean up expired nonces (run periodically)."""
266
+
267
+ result = await self.db.execute("""
268
+ DELETE FROM auth_nonces
269
+ WHERE expires_at < datetime('now')
270
+ """)
271
+
272
+ if result.rowcount > 0:
273
+ self.logger.info(f"Cleaned up {result.rowcount} expired nonces")
@@ -0,0 +1,173 @@
1
+ """Tier manager for managing user tiers based on NFT ownership."""
2
+
3
+ import logging
4
+
5
+ from core.exceptions import TierError, UserNotFoundError
6
+
7
+ # Import credit constants from single source of truth
8
+ from modules.credits.pricing import DEN_SIGNUP_ALLOWANCE
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class TierManager:
14
+ """Manage user tiers based on NFT ownership."""
15
+
16
+ # Tier quotas (SIMPLIFIED: holder = 1+ DEN tokens)
17
+ # Credits: 100 welcome (everyone) + 2000 DEN Sign Up Allowance per token
18
+ TIER_LIMITS = {
19
+ "holder": {
20
+ "signup_allowance_per_token": DEN_SIGNUP_ALLOWANCE, # $20 USD one-time per token ID
21
+ "max_concurrent_sessions": 10,
22
+ "max_steps": 50,
23
+ "allowed_models": ["gpt-5", "claude-sonnet-4-5", "gemini-2.5-flash", "deepseek-chat"]
24
+ },
25
+ "admin": {
26
+ "signup_allowance_per_token": 0, # Admins don't need allowance
27
+ "max_concurrent_sessions": 100,
28
+ "max_steps": 500,
29
+ "allowed_models": "*" # All models
30
+ }
31
+ }
32
+
33
+ def __init__(self, db, alchemy_tool=None):
34
+ """
35
+ Initialize tier manager.
36
+
37
+ Args:
38
+ db: Database manager instance
39
+ alchemy_tool: AlchemyTool instance for NFT checks (optional)
40
+ """
41
+ self.db = db
42
+ self.alchemy_tool = alchemy_tool
43
+ self.logger = logging.getLogger('auth.tier_manager')
44
+
45
+ async def get_user_tier(self, user_id: str) -> str:
46
+ """Get user's current tier.
47
+
48
+ UPDATED: Returns tier as-is (including 'free').
49
+ Access control is enforced at feature level, not here.
50
+ """
51
+
52
+ result = await self.db.fetch_one("""
53
+ SELECT tier FROM user_profiles WHERE user_id = ?
54
+ """, (user_id,))
55
+
56
+ tier = result['tier'] if result else 'free'
57
+
58
+ return tier
59
+
60
+ async def get_tier_limits(self, user_id: str) -> dict:
61
+ """Get quota limits for user's tier.
62
+
63
+ For free tier, returns minimal limits (info only - access blocked at feature level).
64
+ Credits: 100 welcome (everyone) + 2000 DEN Sign Up Allowance per token.
65
+ """
66
+
67
+ tier = await self.get_user_tier(user_id)
68
+
69
+ # Free tier gets minimal limits (shown in UI but features are blocked)
70
+ if tier == 'free':
71
+ return {
72
+ "signup_allowance_per_token": 0,
73
+ "max_concurrent_sessions": 0,
74
+ "max_steps": 0,
75
+ "allowed_models": []
76
+ }
77
+
78
+ # x402 and free_access tiers use holder limits
79
+ # - x402: pay-per-request users
80
+ # - free_access: admin-granted access without DEN token
81
+ if tier in ('x402', 'free_access'):
82
+ return self.TIER_LIMITS['holder']
83
+
84
+ # Return actual limits for holder/admin
85
+ if tier not in self.TIER_LIMITS:
86
+ raise TierError("Invalid tier - contact support")
87
+
88
+ return self.TIER_LIMITS[tier]
89
+
90
+ async def check_quota(self, user_id: str, quota_type: str) -> bool:
91
+ """Check if user has quota for resource."""
92
+
93
+ limits = await self.get_tier_limits(user_id)
94
+
95
+ if quota_type == "concurrent_sessions":
96
+ # Count active sessions
97
+ # Note: This assumes a sessions table exists
98
+ # If not, this check will be skipped
99
+ try:
100
+ active = await self.db.fetch_one("""
101
+ SELECT COUNT(*) as count
102
+ FROM sessions
103
+ WHERE user_id = ? AND status IN ('running', 'paused')
104
+ """, (user_id,))
105
+
106
+ if active:
107
+ return active['count'] < limits['max_concurrent_sessions']
108
+ except Exception as e:
109
+ self.logger.warning(f"Could not check session quota: {e}")
110
+ return True # Allow if we can't check
111
+
112
+ return True
113
+
114
+ async def check_model_allowed(self, user_id: str, model: str) -> bool:
115
+ """Check if user's tier allows this model."""
116
+
117
+ limits = await self.get_tier_limits(user_id)
118
+ allowed = limits['allowed_models']
119
+
120
+ if allowed == "*":
121
+ return True
122
+
123
+ return model in allowed
124
+
125
+ async def get_user_info(self, user_id: str) -> dict:
126
+ """Get comprehensive user tier information."""
127
+
128
+ result = await self.db.fetch_one("""
129
+ SELECT
130
+ u.tier,
131
+ u.wallet_address,
132
+ (u.den_token_count > 0) as has_den_token,
133
+ u.den_token_verified_at,
134
+ c.balance,
135
+ c.lifetime_earned,
136
+ c.lifetime_spent
137
+ FROM user_profiles u
138
+ LEFT JOIN user_credits c ON u.user_id = c.user_id
139
+ WHERE u.user_id = ?
140
+ """, (user_id,))
141
+
142
+ if not result:
143
+ raise UserNotFoundError(f"User not found: {user_id}")
144
+
145
+ tier = result['tier']
146
+
147
+ # Get limits - use get_tier_limits logic for consistency
148
+ if tier == 'free':
149
+ limits = {
150
+ "signup_allowance_per_token": 0,
151
+ "max_concurrent_sessions": 0,
152
+ "max_steps": 0,
153
+ "allowed_models": []
154
+ }
155
+ elif tier in ('x402', 'free_access'):
156
+ # x402 and free_access use holder limits
157
+ limits = self.TIER_LIMITS['holder']
158
+ else:
159
+ limits = self.TIER_LIMITS.get(tier, self.TIER_LIMITS.get('holder', {}))
160
+
161
+ return {
162
+ "user_id": user_id,
163
+ "tier": tier,
164
+ "wallet_address": result['wallet_address'],
165
+ "has_den_token": bool(result['has_den_token']),
166
+ "den_token_verified_at": result['den_token_verified_at'],
167
+ "limits": limits,
168
+ "credits": {
169
+ "balance": result['balance'] or 0,
170
+ "lifetime_earned": result['lifetime_earned'] or 0,
171
+ "lifetime_spent": result['lifetime_spent'] or 0
172
+ }
173
+ }
modules/base_module.py ADDED
@@ -0,0 +1,190 @@
1
+ """Base module class for core functionality modules."""
2
+
3
+ from typing import Dict, Any, Optional, List, TYPE_CHECKING, Type
4
+ import asyncio
5
+ import logging
6
+ from core.base_component import BaseComponent
7
+ from core.config import BotConfig
8
+ from core.exceptions import ModuleError, DependencyError
9
+ from core.logging import get_component_logger
10
+
11
+ if TYPE_CHECKING:
12
+ from core.container import DependencyContainer
13
+ else:
14
+ # Import for runtime use with forward reference
15
+ from core.container import DependencyContainer
16
+
17
+ logger = get_component_logger('modules.base_module')
18
+
19
+ class BaseModule(BaseComponent):
20
+ """Base class for modules."""
21
+
22
+ def __init__(self, name: str, config: BotConfig, container: Optional['DependencyContainer'] = None):
23
+ """Initialize base module.
24
+
25
+ Args:
26
+ name: Module name
27
+ config: Bot configuration
28
+ container: Optional dependency container
29
+ """
30
+ super().__init__(name=name, config=config, container=container)
31
+ self._modules = {} # For module-specific dependencies
32
+ self.logger = get_component_logger(f"modules.{name}")
33
+ self._lock = asyncio.Lock()
34
+ self._initialized = False
35
+ self.component = self # Forward reference
36
+
37
+ @property
38
+ def required_dependencies(self) -> List[str]:
39
+ """Get module's required dependencies."""
40
+ return list(self.required_modules.keys())
41
+
42
+ @property
43
+ def required_modules(self) -> Dict[str, str]:
44
+ """Get required modules with descriptions."""
45
+ return {}
46
+
47
+ @property
48
+ def optional_modules(self) -> Dict[str, str]:
49
+ """Get optional modules."""
50
+ return {}
51
+
52
+ async def initialize(self) -> None:
53
+ """Initialize module."""
54
+ if self._initialized:
55
+ return
56
+
57
+ async with self._lock:
58
+ if self._initialized:
59
+ return
60
+
61
+ try:
62
+ # Register required dependencies from container if available
63
+ if self.container:
64
+ for module_name in self.required_modules.keys():
65
+ service = self.container.get_service(module_name)
66
+ if service:
67
+ self.set_module(module_name, service)
68
+
69
+ # Validate dependencies first
70
+ try:
71
+ self.validate_dependencies()
72
+ except DependencyError as e:
73
+ self.logger.warning(f"Dependency validation failed: {e}")
74
+ # Continue initialization - optional modules might be missing
75
+
76
+ # Call module-specific initialization
77
+ await self._initialize()
78
+ self._initialized = True
79
+
80
+ # Restore INFO level to ensure visibility of successful initialization
81
+ self.logger.info(f"✨ {self.name} initialized successfully")
82
+ except Exception as e:
83
+ self.logger.error(f"Failed to initialize {self.name}: {e}")
84
+ raise ModuleError(f"Failed to initialize module {self.name}: {e}")
85
+
86
+ async def cleanup(self) -> None:
87
+ """Clean up module resources."""
88
+ if not self._initialized:
89
+ return
90
+
91
+ async with self._lock:
92
+ try:
93
+ await self._cleanup()
94
+ self._initialized = False
95
+ self.logger.info(f"{self.name} cleaned up successfully")
96
+ except Exception as e:
97
+ self.logger.error(f"Failed to clean up {self.name}: {e}")
98
+ raise ModuleError(f"Failed to clean up module {self.name}: {e}")
99
+
100
+ async def _initialize(self) -> None:
101
+ """Module-specific initialization."""
102
+ pass
103
+
104
+ async def _cleanup(self) -> None:
105
+ """Module-specific cleanup."""
106
+ pass
107
+
108
+ def set_module(self, name: str, module: Any) -> None:
109
+ """Set a required module."""
110
+ self._modules[name] = module
111
+
112
+ def get_module(self, name: str) -> Optional[Any]:
113
+ """Get a module by name."""
114
+ return self._modules.get(name)
115
+
116
+ def validate_dependencies(self) -> None:
117
+ """Validate module dependencies."""
118
+ missing = []
119
+ for module_name, description in self.required_modules.items():
120
+ # Check both _modules dictionary and container services
121
+ module_exists = module_name in self._modules
122
+ container_has_service = self.container and self.container.has_service(module_name)
123
+
124
+ if not (module_exists or container_has_service):
125
+ missing.append(f"{module_name} ({description})")
126
+
127
+ if missing:
128
+ raise DependencyError(
129
+ f"Module {self.name} missing required modules: {', '.join(missing)}"
130
+ )
131
+
132
+ # Log missing optional modules
133
+ missing_optional = []
134
+ for module_name, description in self.optional_modules.items():
135
+ module_exists = module_name in self._modules
136
+ container_has_service = self.container and self.container.has_service(module_name)
137
+
138
+ if not (module_exists or container_has_service):
139
+ missing_optional.append(f"{module_name} ({description})")
140
+
141
+ if missing_optional:
142
+ self.logger.info(
143
+ f"Module {self.name} missing optional modules: {', '.join(missing_optional)}"
144
+ )
145
+
146
+ async def _validate_dependencies(self) -> None:
147
+ """Validate component dependencies."""
148
+ if not self.container:
149
+ return
150
+
151
+ missing = []
152
+ for dep in self.required_dependencies:
153
+ if not self.container.has_service(dep) and dep not in self._modules:
154
+ missing.append(dep)
155
+
156
+ if missing:
157
+ raise DependencyError(f"Missing required dependencies: {', '.join(missing)}")
158
+
159
+ def get_status(self) -> Dict[str, Any]:
160
+ """Get module status information.
161
+
162
+ Returns:
163
+ Dict containing status information including initialization state
164
+ """
165
+ return {
166
+ "name": self.name,
167
+ "initialized": self._initialized,
168
+ "module_type": self.__class__.__name__,
169
+ "has_container": self.container is not None
170
+ }
171
+
172
+ def initialize_module(module_name: str, module_class: Type, container: DependencyContainer) -> bool:
173
+ """Initialize a module and register it with the container."""
174
+ try:
175
+ logger.debug(f"Initializing module {module_name}...")
176
+
177
+ # Create and initialize the module
178
+ module = module_class(name=module_name, config=container.config, container=container)
179
+ asyncio.run(module.initialize())
180
+
181
+ # Register with container
182
+ container.register_service(module_name, module)
183
+
184
+ # Log success at debug level - higher level logs should come from the initialization module
185
+ logger.debug(f"{module_name} initialized successfully")
186
+ return True
187
+
188
+ except Exception as e:
189
+ logger.error(f"Failed to initialize module {module_name}: {e}")
190
+ return False
@@ -0,0 +1,29 @@
1
+ """Credit system modules for tracking and metering usage."""
2
+
3
+ from .balance_manager import CreditBalanceManager
4
+ from .usage_tracker import LLMUsageTracker, UsageRecord, CostBreakdown
5
+ from .pricing import pricing, PricingConfig, WELCOME_BONUS, DEN_SIGNUP_ALLOWANCE
6
+ from .cost_utils import calculate_cost_from_tokens, calculate_user_cost, get_cost_breakdown
7
+
8
+ # DEPRECATED: UsageMeter - use LLMUsageTracker instead
9
+ # Keeping import for backward compatibility during migration
10
+ from .usage_meter import UsageMeter
11
+
12
+ __all__ = [
13
+ # Core components
14
+ 'CreditBalanceManager',
15
+ 'LLMUsageTracker', # PRIMARY - use this for LLM tracking
16
+ 'UsageRecord',
17
+ 'CostBreakdown',
18
+ 'pricing',
19
+ 'PricingConfig',
20
+ # Credit bonus constants (single source of truth)
21
+ 'WELCOME_BONUS',
22
+ 'DEN_SIGNUP_ALLOWANCE',
23
+ # Shared utilities
24
+ 'calculate_cost_from_tokens',
25
+ 'calculate_user_cost',
26
+ 'get_cost_breakdown',
27
+ # Deprecated
28
+ 'UsageMeter', # DEPRECATED - will be removed in future version
29
+ ]