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.
- agents/README.md +453 -0
- agents/__init__.py +164 -0
- agents/base_agent.py +743 -0
- agents/personality/character.py +119 -0
- agents/personality/character_manager.py +309 -0
- agents/personality/characters/rob.character.json +56 -0
- agents/personality/characters/trump.character.json +63 -0
- agents/personality/persona_render.py +78 -0
- agents/personality/persona_resolver.py +115 -0
- agents/prompt/__init__.py +58 -0
- agents/prompt/base_prompt.py +92 -0
- agents/prompt/system.py +417 -0
- agents/task/README_PATH_MANAGEMENT.md +141 -0
- agents/task/__init__.py +162 -0
- agents/task/agent/__init__.py +70 -0
- agents/task/agent/agent_state.py +408 -0
- agents/task/agent/async_delegation.py +274 -0
- agents/task/agent/autonomy_state.py +275 -0
- agents/task/agent/conversation.py +75 -0
- agents/task/agent/core/__init__.py +6 -0
- agents/task/agent/core/aux_metering.py +61 -0
- agents/task/agent/core/background_review.py +157 -0
- agents/task/agent/core/construction.py +1179 -0
- agents/task/agent/core/conversational_exit.py +49 -0
- agents/task/agent/core/correspondent_gate.py +227 -0
- agents/task/agent/core/curator.py +246 -0
- agents/task/agent/core/episodic_digest.py +103 -0
- agents/task/agent/core/error_recovery.py +480 -0
- agents/task/agent/core/history_io.py +257 -0
- agents/task/agent/core/llm_provisioning.py +287 -0
- agents/task/agent/core/llm_runner.py +547 -0
- agents/task/agent/core/logging_io.py +276 -0
- agents/task/agent/core/loop_detection.py +262 -0
- agents/task/agent/core/memory_prefetch.py +325 -0
- agents/task/agent/core/memory_writer.py +667 -0
- agents/task/agent/core/model_introspection.py +82 -0
- agents/task/agent/core/model_swap.py +177 -0
- agents/task/agent/core/next_action_internal.py +1331 -0
- agents/task/agent/core/output_validation.py +303 -0
- agents/task/agent/core/project_context.py +272 -0
- agents/task/agent/core/resources.py +93 -0
- agents/task/agent/core/result_processing.py +389 -0
- agents/task/agent/core/run_loop.py +661 -0
- agents/task/agent/core/safety_lifecycle.py +228 -0
- agents/task/agent/core/secret_guard.py +293 -0
- agents/task/agent/core/self_wake.py +280 -0
- agents/task/agent/core/session_metadata.py +104 -0
- agents/task/agent/core/step.py +781 -0
- agents/task/agent/core/step_execution.py +398 -0
- agents/task/agent/core/step_telemetry.py +222 -0
- agents/task/agent/core/test_aux_metering.py +50 -0
- agents/task/agent/core/test_billing_failover.py +68 -0
- agents/task/agent/core/test_fatal_error_classifier.py +38 -0
- agents/task/agent/core/test_judge_validation.py +275 -0
- agents/task/agent/core/test_model_swap.py +190 -0
- agents/task/agent/core/test_reflection_offload.py +54 -0
- agents/task/agent/core/turn_input.py +8 -0
- agents/task/agent/core/untrusted_wrap.py +103 -0
- agents/task/agent/core/user_ingress.py +228 -0
- agents/task/agent/hitl_manager.py +396 -0
- agents/task/agent/log_sanitize.py +36 -0
- agents/task/agent/message_manager/config.py +97 -0
- agents/task/agent/message_manager/service.py +758 -0
- agents/task/agent/message_manager/tests.py +305 -0
- agents/task/agent/message_manager/tool_call_builder.py +539 -0
- agents/task/agent/message_manager/tool_message_repair.py +364 -0
- agents/task/agent/message_manager/views.py +143 -0
- agents/task/agent/messages/__init__.py +8 -0
- agents/task/agent/messages/builders.py +309 -0
- agents/task/agent/messages/compactor.py +611 -0
- agents/task/agent/messages/context_references.py +271 -0
- agents/task/agent/messages/filters.py +333 -0
- agents/task/agent/messages/guidance.py +328 -0
- agents/task/agent/messages/persistence.py +448 -0
- agents/task/agent/messages/retrieval.py +506 -0
- agents/task/agent/messages/sqlite_persistence.py +85 -0
- agents/task/agent/messages/test_compaction_upgrade.py +349 -0
- agents/task/agent/messages/test_persistence_subdir_fix.py +28 -0
- agents/task/agent/messages/test_sqlite_persistence.py +54 -0
- agents/task/agent/messages/token_counter.py +532 -0
- agents/task/agent/orchestrator.py +776 -0
- agents/task/agent/profile_manager.py +105 -0
- agents/task/agent/profile_registry.py +173 -0
- agents/task/agent/prompts.py +1090 -0
- agents/task/agent/scenario_registry.py +226 -0
- agents/task/agent/service.py +509 -0
- agents/task/agent/session.py +984 -0
- agents/task/agent/skill_discovery.py +110 -0
- agents/task/agent/skill_frontmatter.py +94 -0
- agents/task/agent/skill_manager.py +1201 -0
- agents/task/agent/skill_store.py +627 -0
- agents/task/agent/skill_validation.py +56 -0
- agents/task/agent/skill_writer.py +492 -0
- agents/task/agent/sub_agent_manager.py +1137 -0
- agents/task/agent/test_session_disk_recovery.py +34 -0
- agents/task/agent/tests.py +513 -0
- agents/task/agent/tool_call_tracker.py +511 -0
- agents/task/agent/views.py +469 -0
- agents/task/config.py +432 -0
- agents/task/constants.py +1384 -0
- agents/task/flag_defaults.py +63 -0
- agents/task/goals/__init__.py +1 -0
- agents/task/goals/autonomy_marker.py +28 -0
- agents/task/goals/board.py +690 -0
- agents/task/goals/completion_judge.py +247 -0
- agents/task/goals/context.py +62 -0
- agents/task/goals/dispatcher.py +848 -0
- agents/task/goals/escalation.py +102 -0
- agents/task/goals/planner.py +114 -0
- agents/task/logging_config.py +155 -0
- agents/task/path.py +991 -0
- agents/task/robust_parse_config.py +400 -0
- agents/task/runtime/__init__.py +0 -0
- agents/task/runtime/run_as_session.py +130 -0
- agents/task/runtime_safety.py +308 -0
- agents/task/session/__init__.py +5 -0
- agents/task/session/browser_pool.py +130 -0
- agents/task/session/cleanup.py +612 -0
- agents/task/session/execution.py +397 -0
- agents/task/session/feed.py +200 -0
- agents/task/session/hitl_ingress.py +201 -0
- agents/task/session/hooks.py +83 -0
- agents/task/session/multi_agent.py +243 -0
- agents/task/session/workspace.py +91 -0
- agents/task/session_registry.py +91 -0
- agents/task/session_route.py +35 -0
- agents/task/sqlite_session_registry.py +261 -0
- agents/task/surface_config.py +185 -0
- agents/task/telemetry/__init__.py +58 -0
- agents/task/telemetry/event_log.py +180 -0
- agents/task/telemetry/formatters.py +642 -0
- agents/task/telemetry/manager.py +571 -0
- agents/task/telemetry/memory_events.py +66 -0
- agents/task/telemetry/self_events.py +53 -0
- agents/task/telemetry/sequence.py +160 -0
- agents/task/telemetry/service.py +1265 -0
- agents/task/telemetry/views.py +882 -0
- agents/task/templates.py +98 -0
- agents/task/test_aux_chain.py +151 -0
- agents/task/test_aux_router.py +41 -0
- agents/task/tests/test_parse_robustness.py +170 -0
- agents/task/tool_defaults.py +110 -0
- agents/task/utils.py +762 -0
- agents/task/utils_json.py +768 -0
- agents/task/utils_webview.py +86 -0
- agents/task/workspace_context.py +229 -0
- agents/task_agent_lite.py +2398 -0
- api/README.md +683 -0
- api/TASK_API_DOCS.md +151 -0
- api/__init__.py +0 -0
- api/a2a/__init__.py +37 -0
- api/a2a/agent_card.py +337 -0
- api/a2a/client.py +657 -0
- api/a2a/endpoints.py +375 -0
- api/a2a/models.py +268 -0
- api/a2a/streaming.py +419 -0
- api/a2a/task_handler.py +929 -0
- api/admin_endpoints.py +1584 -0
- api/app.py +923 -0
- api/auth_constants.py +28 -0
- api/auth_endpoints.py +402 -0
- api/auth_state.py +36 -0
- api/chat_via_task.py +42 -0
- api/conversation_manager.py +231 -0
- api/dependencies.py +186 -0
- api/eip8004_endpoints.py +452 -0
- api/hyperliquid_models.py +174 -0
- api/hyperliquid_routes.py +450 -0
- api/interfaces.py +207 -0
- api/jwt_middleware.py +83 -0
- api/kb/__init__.py +1 -0
- api/kb/endpoints.py +314 -0
- api/mcp_models.py +279 -0
- api/mcp_routes.py +438 -0
- api/middleware.py +526 -0
- api/models.py +176 -0
- api/openai_compat/__init__.py +0 -0
- api/openai_compat/model_map.py +25 -0
- api/openai_compat/models.py +59 -0
- api/openai_compat/router.py +140 -0
- api/payment_endpoints.py +444 -0
- api/payment_verification.py +138 -0
- api/polymarket_models.py +155 -0
- api/polymarket_routes.py +501 -0
- api/pricing_endpoints.py +89 -0
- api/session_routing.py +54 -0
- api/skill_endpoints.py +453 -0
- api/task_http_api.py +1856 -0
- api/webhooks.py +52 -0
- api/x402_endpoints.py +280 -0
- avatar/README.md +44 -0
- avatar/__init__.py +1 -0
- avatar/config/rob.json +7 -0
- avatar/mindprint.js +395 -0
- avatar/renders/rob.meta.json +20 -0
- avatar/renders/rob.png +0 -0
- avatar/studio.html +373 -0
- avatar/webview/avatar-live.js +44 -0
- cli/README.md +89 -0
- cli/__init__.py +0 -0
- cli/cli_surface.py +67 -0
- cli/commands/__init__.py +0 -0
- cli/commands/_bootstrap.py +49 -0
- cli/commands/_errors.py +53 -0
- cli/commands/chat.py +973 -0
- cli/commands/config.py +100 -0
- cli/commands/dashboard.py +103 -0
- cli/commands/doctor.py +222 -0
- cli/commands/email.py +174 -0
- cli/commands/gateway.py +346 -0
- cli/commands/goals.py +509 -0
- cli/commands/init.py +276 -0
- cli/commands/journey.py +30 -0
- cli/commands/kb.py +280 -0
- cli/commands/model.py +107 -0
- cli/commands/owner.py +431 -0
- cli/commands/pfp.py +232 -0
- cli/commands/run.py +414 -0
- cli/commands/serve.py +41 -0
- cli/commands/session.py +684 -0
- cli/commands/skill_install.py +786 -0
- cli/commands/skills.py +128 -0
- cli/commands/subagents.py +100 -0
- cli/commands/surface.py +81 -0
- cli/commands/telegram.py +185 -0
- cli/commands/todos.py +209 -0
- cli/commands/tools.py +102 -0
- cli/commands/update.py +316 -0
- cli/commands/whatsapp.py +175 -0
- cli/config_store.py +276 -0
- cli/gitignore.py +43 -0
- cli/inventory.py +46 -0
- cli/keys.py +101 -0
- cli/persona.py +17 -0
- cli/polyrob.py +137 -0
- cli/ui/__init__.py +64 -0
- cli/ui/activity.py +141 -0
- cli/ui/app.py +538 -0
- cli/ui/banner.py +233 -0
- cli/ui/blocks.py +534 -0
- cli/ui/bootstrap_notice.py +47 -0
- cli/ui/commands/__init__.py +41 -0
- cli/ui/commands/h_cron.py +64 -0
- cli/ui/commands/h_journey.py +164 -0
- cli/ui/commands/h_kb.py +121 -0
- cli/ui/commands/h_learn.py +100 -0
- cli/ui/commands/h_mcp.py +215 -0
- cli/ui/commands/h_self.py +113 -0
- cli/ui/commands/h_skills.py +314 -0
- cli/ui/commands/handlers.py +1672 -0
- cli/ui/commands/registry.py +426 -0
- cli/ui/dialog.py +425 -0
- cli/ui/event_registry.py +110 -0
- cli/ui/event_specs.py +45 -0
- cli/ui/events.py +433 -0
- cli/ui/identity.py +15 -0
- cli/ui/lifecycle.py +159 -0
- cli/ui/live_hooks.py +43 -0
- cli/ui/model_selector.py +532 -0
- cli/ui/persistent_loop.py +181 -0
- cli/ui/pick.py +113 -0
- cli/ui/plain_renderer.py +351 -0
- cli/ui/renderer.py +384 -0
- cli/ui/rich_renderer.py +533 -0
- cli/ui/secrets.py +115 -0
- cli/ui/state.py +376 -0
- cli/ui/statusbar.py +231 -0
- cli/ui/streaming.py +254 -0
- cli/ui/terminal_render.py +105 -0
- cli/ui/theme.py +132 -0
- cli/update/__init__.py +6 -0
- cli/update/context.py +121 -0
- cli/update/detect.py +176 -0
- cli/update/engine.py +95 -0
- cli/update/migrate_guarded.py +55 -0
- cli/update/process_guard.py +215 -0
- cli/update/runners.py +78 -0
- cli/update/snapshot.py +270 -0
- cli/update/versions.py +205 -0
- core/README.md +377 -0
- core/__init__.py +174 -0
- core/assets.py +87 -0
- core/async_bridge.py +76 -0
- core/autonomy_runtime.py +259 -0
- core/base_component.py +302 -0
- core/bootstrap.py +681 -0
- core/bot.py +310 -0
- core/config.py +833 -0
- core/constants.py +126 -0
- core/container.py +462 -0
- core/db_manifest.py +101 -0
- core/embedding.py +57 -0
- core/env.py +39 -0
- core/exceptions.py +287 -0
- core/flags.py +144 -0
- core/flags_catalog.py +345 -0
- core/home_migration.py +62 -0
- core/identity.py +118 -0
- core/initialization.py +1004 -0
- core/instance.py +537 -0
- core/interactive_gate.py +96 -0
- core/logging.py +436 -0
- core/owner_doc_writer.py +176 -0
- core/pairing.py +193 -0
- core/path_safety.py +20 -0
- core/paths.py +27 -0
- core/payment_config.py +21 -0
- core/permissions.py +322 -0
- core/runtime_config.py +96 -0
- core/runtime_paths.py +135 -0
- core/seams.py +35 -0
- core/secret_scan.py +25 -0
- core/secret_scrub.py +71 -0
- core/secrets.py +35 -0
- core/security_logging_filter.py +148 -0
- core/self_context_writer.py +333 -0
- core/self_evolution.py +294 -0
- core/session_context.py +47 -0
- core/sqlite_util.py +59 -0
- core/surfaces/__init__.py +16 -0
- core/surfaces/access.py +118 -0
- core/surfaces/binding.py +88 -0
- core/surfaces/bootstrap.py +78 -0
- core/surfaces/circuit.py +168 -0
- core/surfaces/continuity.py +77 -0
- core/surfaces/correspondents.py +228 -0
- core/surfaces/dispatcher.py +216 -0
- core/surfaces/envelopes.py +75 -0
- core/surfaces/gc.py +30 -0
- core/surfaces/idempotency.py +57 -0
- core/surfaces/inbound_webhook.py +124 -0
- core/surfaces/media.py +32 -0
- core/surfaces/message_router.py +91 -0
- core/surfaces/outbound_allowlist.py +80 -0
- core/surfaces/outbound_dispatcher.py +110 -0
- core/surfaces/outbound_mirror.py +37 -0
- core/surfaces/outbound_queue.py +107 -0
- core/surfaces/outbound_target.py +12 -0
- core/surfaces/owner_admin.py +29 -0
- core/surfaces/proactive.py +78 -0
- core/surfaces/progress.py +108 -0
- core/surfaces/rate_bucket.py +21 -0
- core/surfaces/registry.py +51 -0
- core/surfaces/rendering.py +53 -0
- core/surfaces/send_policy.py +14 -0
- core/surfaces/serialize.py +24 -0
- core/surfaces/session_chat_registry.py +112 -0
- core/surfaces/session_policy.py +60 -0
- core/surfaces/surface.py +288 -0
- core/surfaces/transcription.py +88 -0
- core/surfaces/voice_echo.py +26 -0
- core/surfaces/voice_guard.py +23 -0
- core/tickers.py +174 -0
- core/tool_catalog.py +192 -0
- core/version.py +75 -0
- core/wallet/__init__.py +5 -0
- core/wallet/agent_wallet.py +59 -0
- core/wallet/audit_sink.py +61 -0
- core/wallet/config.py +70 -0
- core/wallet/factory.py +87 -0
- core/wallet/policy.py +104 -0
- core/wallet/signer.py +51 -0
- cron/README.md +65 -0
- cron/__init__.py +0 -0
- cron/delivery.py +277 -0
- cron/digest.py +124 -0
- cron/jobs.py +207 -0
- cron/runner.py +297 -0
- cron/schedule.py +194 -0
- cron/scheduler.py +201 -0
- cron/service.py +57 -0
- cron/wake_gate.py +223 -0
- data/__init__.py +1 -0
- data/prompts/autov2_prompts.json +87 -0
- data/prompts/skills/browser-automation/SKILL.md +43 -0
- data/prompts/skills/coding-workflow/SKILL.md +36 -0
- data/prompts/skills/crypto-trading-safety/SKILL.md +53 -0
- data/prompts/skills/document-writing/SKILL.md +33 -0
- data/prompts/skills/email-comms/SKILL.md +33 -0
- data/prompts/skills/file-data-ops/SKILL.md +35 -0
- data/prompts/skills/hyperliquid-account-review/SKILL.md +48 -0
- data/prompts/skills/hyperliquid-market-data/SKILL.md +52 -0
- data/prompts/skills/hyperliquid-trading/SKILL.md +59 -0
- data/prompts/skills/lead-research/SKILL.md +86 -0
- data/prompts/skills/market-research-brief/SKILL.md +104 -0
- data/prompts/skills/person-analyzer/SKILL.md +124 -0
- data/prompts/skills/polymarket-market-research/SKILL.md +54 -0
- data/prompts/skills/polymarket-portfolio-review/SKILL.md +50 -0
- data/prompts/skills/polymarket-trading/SKILL.md +55 -0
- data/prompts/skills/presentation-creator/SKILL.md +36 -0
- data/prompts/skills/project-analyzer/SKILL.md +169 -0
- data/prompts/skills/rules.json +268 -0
- data/prompts/skills/secret-handling/SKILL.md +20 -0
- data/prompts/skills/skill-authoring/SKILL.md +31 -0
- data/prompts/skills/skill-security-review/SKILL.md +27 -0
- data/prompts/skills/social-discovery/SKILL.md +57 -0
- data/prompts/skills/task-planning/SKILL.md +33 -0
- data/prompts/skills/web-research/SKILL.md +39 -0
- data/prompts/skills/web-scraping/SKILL.md +73 -0
- data/prompts/skills/x-engagement/SKILL.md +69 -0
- data/prompts/system_prompts.json +4 -0
- data/subscription_channels.json +5 -0
- modules/README.md +503 -0
- modules/__init__.py +124 -0
- modules/auth/__init__.py +13 -0
- modules/auth/api_key_manager.py +157 -0
- modules/auth/identity_mapper.py +380 -0
- modules/auth/siwe_auth.py +273 -0
- modules/auth/tier_manager.py +173 -0
- modules/base_module.py +190 -0
- modules/credits/__init__.py +29 -0
- modules/credits/balance_manager.py +237 -0
- modules/credits/cost_utils.py +129 -0
- modules/credits/pricing.py +139 -0
- modules/credits/unified_ledger.py +148 -0
- modules/credits/usage_meter.py +310 -0
- modules/credits/usage_tracker.py +674 -0
- modules/database/__init__.py +33 -0
- modules/database/audit_log.py +322 -0
- modules/database/auth_tables.py +276 -0
- modules/database/connection.py +644 -0
- modules/database/connection_pool.py +305 -0
- modules/database/conversation_contexts.py +446 -0
- modules/database/database_manager.py +237 -0
- modules/database/hyperliquid.py +375 -0
- modules/database/polymarket.py +574 -0
- modules/database/user_mcp_servers.py +728 -0
- modules/database/user_profiles.py +562 -0
- modules/database/utils.py +107 -0
- modules/database/x402_tables.py +92 -0
- modules/eip8004/README.md +1069 -0
- modules/eip8004/__init__.py +65 -0
- modules/eip8004/contracts.py +498 -0
- modules/eip8004/models.py +271 -0
- modules/eip8004/registration.py +148 -0
- modules/eip8004/reputation.py +367 -0
- modules/eip8004/validation.py +277 -0
- modules/llm/__init__.py +97 -0
- modules/llm/adapters.py +963 -0
- modules/llm/anthropic_client.py +831 -0
- modules/llm/available_models.py +106 -0
- modules/llm/brain_scrubber.py +137 -0
- modules/llm/cache_hints.py +162 -0
- modules/llm/deepseek_client.py +562 -0
- modules/llm/gemini_client.py +1367 -0
- modules/llm/llm_client.py +1139 -0
- modules/llm/llm_client_registry.py +188 -0
- modules/llm/llm_factory.py +237 -0
- modules/llm/llm_manager.py +1036 -0
- modules/llm/messages.py +374 -0
- modules/llm/model_registry.py +1935 -0
- modules/llm/nvidia_client.py +125 -0
- modules/llm/openai_client.py +800 -0
- modules/llm/openrouter_client.py +969 -0
- modules/llm/profiles.py +178 -0
- modules/llm/test_anthropic_cache_capture.py +35 -0
- modules/llm/test_cached_pricing_derivation.py +24 -0
- modules/llm/think_scrubber.py +444 -0
- modules/llm/token_counter.py +268 -0
- modules/memory/__init__.py +35 -0
- modules/memory/backend_factory.py +83 -0
- modules/memory/cache_manager.py +186 -0
- modules/memory/episodic.py +108 -0
- modules/memory/local_vector_memory_provider.py +552 -0
- modules/memory/memory_manager.py +225 -0
- modules/memory/models.py +312 -0
- modules/memory/provider.py +210 -0
- modules/memory/registry.py +205 -0
- modules/memory/sqlite_memory_provider.py +767 -0
- modules/memory/task/__init__.py +52 -0
- modules/memory/task/compaction_manager.py +157 -0
- modules/memory/task/context_retriever.py +809 -0
- modules/memory/task/hierarchical_memory.py +1002 -0
- modules/memory/task/lexical_retriever.py +71 -0
- modules/memory/task/null_context_manager.py +213 -0
- modules/memory/task/phase_manager.py +625 -0
- modules/memory/task/reflection_service.py +157 -0
- modules/memory/task/semantic_retriever.py +218 -0
- modules/memory/task/task_context_manager.py +1165 -0
- modules/memory/task/test_forgetting_engages.py +24 -0
- modules/memory/task/test_reflection_llm.py +39 -0
- modules/memory/task/test_threat_scan.py +53 -0
- modules/memory/task/threat_scan.py +90 -0
- modules/memory/test_sqlite_memory_provider.py +106 -0
- modules/memory/user_profile_manager.py +527 -0
- modules/payments/__init__.py +11 -0
- modules/payments/deposit_monitor.py +478 -0
- modules/payments/price_oracle.py +70 -0
- modules/payments/treasury_sweeper.py +401 -0
- modules/payments/wallet_generator.py +145 -0
- modules/pfp/__init__.py +1 -0
- modules/pfp/config.py +92 -0
- modules/pfp/mesh.py +565 -0
- modules/pfp/push.py +69 -0
- modules/pfp/renderer.py +85 -0
- modules/pfp/store.py +82 -0
- modules/skills/__init__.py +1 -0
- modules/skills/skill_usage.py +248 -0
- modules/transcription/__init__.py +24 -0
- modules/transcription/base.py +20 -0
- modules/transcription/faster_whisper_transcriber.py +55 -0
- modules/x402/README.md +249 -0
- modules/x402/__init__.py +22 -0
- modules/x402/invoicing.py +416 -0
- modules/x402/middleware.py +364 -0
- modules/x402/settlement_watcher.py +145 -0
- modules/x402/x402_integration.py +344 -0
- polyrob-0.5.0.dist-info/METADATA +482 -0
- polyrob-0.5.0.dist-info/RECORD +745 -0
- polyrob-0.5.0.dist-info/WHEEL +5 -0
- polyrob-0.5.0.dist-info/entry_points.txt +2 -0
- polyrob-0.5.0.dist-info/licenses/LICENSE +21 -0
- polyrob-0.5.0.dist-info/top_level.txt +12 -0
- surfaces/README.md +70 -0
- surfaces/__init__.py +0 -0
- surfaces/email/__init__.py +0 -0
- surfaces/email/dedup.py +44 -0
- surfaces/email/harness.py +187 -0
- surfaces/email/inbound.py +159 -0
- surfaces/email/seed.py +60 -0
- surfaces/email/surface.py +105 -0
- surfaces/telegram/__init__.py +0 -0
- surfaces/telegram/dedup.py +83 -0
- surfaces/telegram/harness.py +886 -0
- surfaces/telegram/inbound.py +145 -0
- surfaces/telegram/interactive_tools.py +49 -0
- surfaces/telegram/markdown.py +347 -0
- surfaces/telegram/rate_limit.py +167 -0
- surfaces/telegram/surface.py +187 -0
- surfaces/telegram/voice.py +52 -0
- surfaces/whatsapp/__init__.py +0 -0
- surfaces/whatsapp/client.py +51 -0
- surfaces/whatsapp/harness.py +62 -0
- surfaces/whatsapp/inbound.py +132 -0
- surfaces/whatsapp/surface.py +64 -0
- surfaces/whatsapp/window.py +34 -0
- tools/README.md +578 -0
- tools/__init__.py +424 -0
- tools/alchemy/__init__.py +5 -0
- tools/alchemy/alchemy_tool.py +389 -0
- tools/anysite/__init__.py +17 -0
- tools/anysite/client.py +88 -0
- tools/anysite/tool.py +89 -0
- tools/base_tool.py +468 -0
- tools/browser/__init__.py +22 -0
- tools/browser/actions.py +118 -0
- tools/browser/browser.py +1457 -0
- tools/browser/browser_manager.py +690 -0
- tools/browser/context.py +1748 -0
- tools/browser/playwright_utils.py +137 -0
- tools/browser/views.py +56 -0
- tools/code_exec/SANDBOX_SECURITY.md +144 -0
- tools/code_exec/__init__.py +178 -0
- tools/code_exec/backend.py +73 -0
- tools/code_exec/backends/__init__.py +1 -0
- tools/code_exec/backends/docker.py +779 -0
- tools/code_exec/backends/local_subprocess.py +154 -0
- tools/code_exec/env_policy.py +45 -0
- tools/code_exec/result.py +42 -0
- tools/code_exec/sandbox_guard.py +59 -0
- tools/code_exec/tool.py +227 -0
- tools/coding/__init__.py +50 -0
- tools/coding/edit.py +107 -0
- tools/coding/search.py +113 -0
- tools/coding/tool.py +353 -0
- tools/collabland/__init__.py +5 -0
- tools/collabland/collabland_tool.py +471 -0
- tools/controller/__init__.py +0 -0
- tools/controller/_helpers.py +165 -0
- tools/controller/action_registration.py +1765 -0
- tools/controller/approval.py +244 -0
- tools/controller/approval_interactive.py +90 -0
- tools/controller/delegation.py +195 -0
- tools/controller/execution.py +797 -0
- tools/controller/execution_context.py +141 -0
- tools/controller/hooks.py +135 -0
- tools/controller/introspection.py +344 -0
- tools/controller/mcp_registrar.py +190 -0
- tools/controller/message_send.py +31 -0
- tools/controller/registry/__init__.py +4 -0
- tools/controller/registry/schema_generators.py +395 -0
- tools/controller/registry/schema_sanitizer.py +406 -0
- tools/controller/registry/service.py +1266 -0
- tools/controller/registry/views.py +151 -0
- tools/controller/service.py +409 -0
- tools/controller/tool_management.py +334 -0
- tools/controller/types.py +28 -0
- tools/controller/views.py +450 -0
- tools/cronjob_tools.py +166 -0
- tools/crypto_trade_gate.py +59 -0
- tools/descriptors.py +452 -0
- tools/dom/__init__.py +0 -0
- tools/dom/history_tree_processor/service.py +244 -0
- tools/dom/history_tree_processor/view.py +99 -0
- tools/dom/service.py +471 -0
- tools/dom/views.py +346 -0
- tools/email_tool.py +403 -0
- tools/exceptions.py +169 -0
- tools/filesystem.py +1038 -0
- tools/filesystem_docproc.py +331 -0
- tools/filesystem_pdf.py +755 -0
- tools/git/__init__.py +42 -0
- tools/git/tool.py +258 -0
- tools/github/__init__.py +33 -0
- tools/github/client.py +94 -0
- tools/github/tool.py +237 -0
- tools/goal_tools.py +309 -0
- tools/hyperliquid/__init__.py +56 -0
- tools/hyperliquid/models.py +233 -0
- tools/hyperliquid/service.py +1680 -0
- tools/knowledge_ingest.py +950 -0
- tools/mcp/README.md +535 -0
- tools/mcp/__init__.py +52 -0
- tools/mcp/catalog.py +115 -0
- tools/mcp/config.py +337 -0
- tools/mcp/mcp_tool.py +1415 -0
- tools/mcp/param_coercion.py +289 -0
- tools/mcp/protocol.py +1520 -0
- tools/mcp/rate_limit.py +50 -0
- tools/mcp/security.py +424 -0
- tools/mcp/self_install.py +111 -0
- tools/mcp/server_manager.py +829 -0
- tools/mcp/subscriptions.py +72 -0
- tools/mcp/user_mcp_service.py +899 -0
- tools/mcp/validation_tracker.py +89 -0
- tools/mcp/views.py +346 -0
- tools/oauth/__init__.py +17 -0
- tools/oauth/manager.py +75 -0
- tools/oauth/provider.py +69 -0
- tools/oauth/providers/__init__.py +1 -0
- tools/oauth/providers/generic_oauth2.py +96 -0
- tools/perplexity_tool.py +280 -0
- tools/polymarket/__init__.py +41 -0
- tools/polymarket/clob_adapter.py +76 -0
- tools/polymarket/models.py +182 -0
- tools/polymarket/service.py +2082 -0
- tools/self_env/__init__.py +61 -0
- tools/self_env/tool.py +274 -0
- tools/shell/__init__.py +80 -0
- tools/shell/backend_pool.py +118 -0
- tools/shell/discipline.py +62 -0
- tools/shell/executor.py +157 -0
- tools/shell/loopback_allow.py +87 -0
- tools/shell/process_registry.py +113 -0
- tools/shell/process_tool.py +131 -0
- tools/shell/state.py +129 -0
- tools/shell/tool.py +151 -0
- tools/task_tool.py +637 -0
- tools/twitter_tool.py +2086 -0
- tools/user_directory.py +179 -0
- tools/web_fetch/__init__.py +3 -0
- tools/web_fetch/fetcher.py +144 -0
- tools/web_fetch/render.py +44 -0
- tools/web_fetch/tool.py +60 -0
- tools/x402/__init__.py +60 -0
- tools/x402/client.py +47 -0
- tools/x402/invoice_tool.py +143 -0
- tools/x402/real_client.py +250 -0
- tools/x402/service.py +131 -0
- utils/README.md +498 -0
- utils/__init__.py +46 -0
- utils/auth_utils.py +114 -0
- utils/bounded_collections.py +162 -0
- utils/circuit_breaker.py +389 -0
- utils/gif_utils.py +435 -0
- utils/markdown_utils.py +291 -0
- utils/message_utils.py +147 -0
- utils/metrics.py +36 -0
- utils/path_validator.py +227 -0
- utils/rate_limit_manager.py +399 -0
- utils/result_size.py +296 -0
- utils/time_utils.py +206 -0
- utils/user_utils.py +188 -0
- webview/README.md +111 -0
- webview/RENAME_AND_ALIGN_HANDOFF.md +200 -0
- webview/__init__.py +8 -0
- webview/activity.py +636 -0
- webview/owner_auth.py +108 -0
- webview/pages.py +448 -0
- webview/repair_sessions.py +399 -0
- webview/server.py +4323 -0
- webview/server_launcher.py +112 -0
- webview/static/css/activity.css +89 -0
- webview/static/css/chat.css +1772 -0
- webview/static/css/components.css +1190 -0
- webview/static/css/config-panel.css +651 -0
- webview/static/css/pages/admin.css +766 -0
- webview/static/css/pages/profile.css +231 -0
- webview/static/css/pages/settings.css +297 -0
- webview/static/css/pages/signin.css +316 -0
- webview/static/css/style.css +4250 -0
- webview/static/css/variables.css +147 -0
- webview/static/css/workspace-fullwidth-fix.css +109 -0
- webview/static/img/favicon.ico +0 -0
- webview/static/js/activity.js +394 -0
- webview/static/js/admin/activity.js +186 -0
- webview/static/js/admin/dashboard.js +163 -0
- webview/static/js/admin/user_detail.js +400 -0
- webview/static/js/admin/users.js +174 -0
- webview/static/js/admin/utils.js +214 -0
- webview/static/js/chat.js +3178 -0
- webview/static/js/config-panel.js +522 -0
- webview/static/js/constants.js +20 -0
- webview/static/js/error-handler.js +226 -0
- webview/static/js/ethers.min.js +1 -0
- webview/static/js/event-filter.js +137 -0
- webview/static/js/event-store.js +351 -0
- webview/static/js/file-attachments.js +374 -0
- webview/static/js/file-loader.js +163 -0
- webview/static/js/index.js +103 -0
- webview/static/js/performance-utils.js +196 -0
- webview/static/js/profile.js +396 -0
- webview/static/js/screenshot.js +386 -0
- webview/static/js/session.js +1527 -0
- webview/static/js/settings.js +1288 -0
- webview/static/js/sidebar-data.js +766 -0
- webview/static/js/sidebar-toggle.js +296 -0
- webview/static/js/socket.io.min.js +7 -0
- webview/static/js/stats.js +355 -0
- webview/static/js/ui-utils.js +420 -0
- webview/static/js/workspace.js +966 -0
- webview/stats_service.py +613 -0
- webview/templates/__init__.py +1 -0
- webview/templates/__pycache__/__init__.cpython-311.pyc +0 -0
- webview/templates/activity.html +36 -0
- webview/templates/admin/activity.html +122 -0
- webview/templates/admin/dashboard.html +112 -0
- webview/templates/admin/user_detail.html +228 -0
- webview/templates/admin/users.html +105 -0
- webview/templates/autonomy.html +60 -0
- webview/templates/error.html +133 -0
- webview/templates/finance.html +90 -0
- webview/templates/identity.html +73 -0
- webview/templates/index.html +42 -0
- webview/templates/layout.html +125 -0
- webview/templates/memory.html +59 -0
- webview/templates/owner_login.html +15 -0
- webview/templates/profile.html +121 -0
- webview/templates/session.html +312 -0
- webview/templates/settings.html +534 -0
- webview/templates/sidebar.html +112 -0
- webview/templates/signin.html +213 -0
- webview/templates/status.html +14 -0
- webview/templates/system.html +45 -0
- 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
|
+
]
|