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,644 @@
|
|
|
1
|
+
import aiosqlite
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Optional, List, Any, Dict, Union, Tuple
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
import sqlite3
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from core.exceptions import DatabaseError
|
|
12
|
+
|
|
13
|
+
class DatabaseConnection:
|
|
14
|
+
"""Manages SQLite database connection."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, db_path: Path, logger: Optional[logging.Logger] = None):
|
|
17
|
+
"""Initialize database connection.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
db_path: Path to SQLite database file
|
|
21
|
+
logger: Optional logger instance
|
|
22
|
+
"""
|
|
23
|
+
self.db_path = db_path
|
|
24
|
+
self.logger = logger or logging.getLogger(__name__)
|
|
25
|
+
self.connection: Optional[sqlite3.Connection] = None
|
|
26
|
+
self._lock = asyncio.Lock()
|
|
27
|
+
self._transaction_lock = asyncio.Lock()
|
|
28
|
+
self._in_transaction = False
|
|
29
|
+
|
|
30
|
+
async def connect(self) -> None:
|
|
31
|
+
"""Establish database connection."""
|
|
32
|
+
try:
|
|
33
|
+
self.connection = sqlite3.connect(
|
|
34
|
+
self.db_path,
|
|
35
|
+
check_same_thread=False
|
|
36
|
+
)
|
|
37
|
+
self.connection.row_factory = sqlite3.Row
|
|
38
|
+
|
|
39
|
+
# SECURITY FIX: Enable foreign key constraints for data integrity
|
|
40
|
+
# FK constraints ensure referential integrity and prevent orphaned records
|
|
41
|
+
# Schema uses ON DELETE CASCADE to handle user deletions properly
|
|
42
|
+
self.connection.execute("PRAGMA foreign_keys = ON")
|
|
43
|
+
|
|
44
|
+
# Clean up orphaned records that might have accumulated when FK was disabled
|
|
45
|
+
await self._cleanup_orphaned_records()
|
|
46
|
+
|
|
47
|
+
self.logger.debug(f"Connected to database at {self.db_path} (FK: ON)")
|
|
48
|
+
|
|
49
|
+
except Exception as e:
|
|
50
|
+
self.logger.error(f"Failed to connect to database: {e}")
|
|
51
|
+
raise DatabaseError(f"Failed to connect to database: {e}")
|
|
52
|
+
|
|
53
|
+
async def close(self) -> None:
|
|
54
|
+
"""Close database connection."""
|
|
55
|
+
if self.connection:
|
|
56
|
+
self.connection.close()
|
|
57
|
+
self.connection = None
|
|
58
|
+
self.logger.debug("Database connection closed")
|
|
59
|
+
|
|
60
|
+
async def _cleanup_orphaned_records(self) -> None:
|
|
61
|
+
"""Clean up orphaned records that accumulated while FK constraints were disabled.
|
|
62
|
+
|
|
63
|
+
This runs once at connection time to fix any data integrity issues
|
|
64
|
+
before FK constraints are enforced.
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
# Temporarily disable FK for cleanup (we're fixing old data)
|
|
68
|
+
self.connection.execute("PRAGMA foreign_keys = OFF")
|
|
69
|
+
|
|
70
|
+
# Tables with FK to user_profiles(user_id)
|
|
71
|
+
orphan_tables = [
|
|
72
|
+
'api_keys',
|
|
73
|
+
'user_credits',
|
|
74
|
+
'credit_transactions',
|
|
75
|
+
'usage_records',
|
|
76
|
+
'user_deposit_addresses',
|
|
77
|
+
'crypto_payments',
|
|
78
|
+
'pending_sweeps',
|
|
79
|
+
'wallet_history',
|
|
80
|
+
'blocked_users',
|
|
81
|
+
'conversation_contexts',
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
total_cleaned = 0
|
|
85
|
+
for table in orphan_tables:
|
|
86
|
+
try:
|
|
87
|
+
# Check if table exists before cleaning
|
|
88
|
+
cursor = self.connection.execute(
|
|
89
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
|
90
|
+
(table,)
|
|
91
|
+
)
|
|
92
|
+
if cursor.fetchone() is None:
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
# Delete orphaned records
|
|
96
|
+
cursor = self.connection.execute(f"""
|
|
97
|
+
DELETE FROM {table}
|
|
98
|
+
WHERE user_id IS NOT NULL
|
|
99
|
+
AND user_id NOT IN (SELECT user_id FROM user_profiles)
|
|
100
|
+
""")
|
|
101
|
+
if cursor.rowcount > 0:
|
|
102
|
+
total_cleaned += cursor.rowcount
|
|
103
|
+
self.logger.info(f"Cleaned {cursor.rowcount} orphaned records from {table}")
|
|
104
|
+
except Exception as e:
|
|
105
|
+
# Log but continue - some tables may not exist yet
|
|
106
|
+
self.logger.debug(f"Could not clean {table}: {e}")
|
|
107
|
+
|
|
108
|
+
# Clean up expired nonces
|
|
109
|
+
try:
|
|
110
|
+
cursor = self.connection.execute("""
|
|
111
|
+
DELETE FROM auth_nonces WHERE expires_at < datetime('now')
|
|
112
|
+
""")
|
|
113
|
+
if cursor.rowcount > 0:
|
|
114
|
+
self.logger.info(f"Cleaned {cursor.rowcount} expired nonces")
|
|
115
|
+
except Exception:
|
|
116
|
+
pass # Table may not exist
|
|
117
|
+
|
|
118
|
+
self.connection.commit()
|
|
119
|
+
|
|
120
|
+
# Re-enable FK constraints
|
|
121
|
+
self.connection.execute("PRAGMA foreign_keys = ON")
|
|
122
|
+
|
|
123
|
+
if total_cleaned > 0:
|
|
124
|
+
self.logger.info(f"✅ Cleaned up {total_cleaned} total orphaned records")
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
self.logger.warning(f"Error during orphan cleanup: {e}")
|
|
128
|
+
# Re-enable FK even if cleanup failed
|
|
129
|
+
self.connection.execute("PRAGMA foreign_keys = ON")
|
|
130
|
+
|
|
131
|
+
async def _initialize_base_schema(self) -> None:
|
|
132
|
+
"""Initialize base tables schema."""
|
|
133
|
+
try:
|
|
134
|
+
# Create base tables first
|
|
135
|
+
base_tables = """
|
|
136
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
137
|
+
version INTEGER PRIMARY KEY,
|
|
138
|
+
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
CREATE TABLE IF NOT EXISTS user_profiles (
|
|
142
|
+
user_id TEXT PRIMARY KEY,
|
|
143
|
+
is_bot BOOLEAN,
|
|
144
|
+
first_name TEXT,
|
|
145
|
+
last_name TEXT,
|
|
146
|
+
username TEXT,
|
|
147
|
+
language_code TEXT,
|
|
148
|
+
role TEXT DEFAULT 'user',
|
|
149
|
+
preferences TEXT,
|
|
150
|
+
wallet_address TEXT,
|
|
151
|
+
den_password TEXT,
|
|
152
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
153
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
CREATE TABLE IF NOT EXISTS conversation_contexts (
|
|
157
|
+
conversation_id TEXT PRIMARY KEY,
|
|
158
|
+
type TEXT NOT NULL,
|
|
159
|
+
user_id TEXT,
|
|
160
|
+
chat_id TEXT NOT NULL,
|
|
161
|
+
chat_name TEXT,
|
|
162
|
+
messages TEXT NOT NULL,
|
|
163
|
+
metadata TEXT,
|
|
164
|
+
last_interaction TIMESTAMP,
|
|
165
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
166
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
167
|
+
FOREIGN KEY (user_id) REFERENCES user_profiles(user_id)
|
|
168
|
+
);
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
# Execute base tables creation
|
|
172
|
+
statements = [stmt.strip() for stmt in base_tables.split(';') if stmt.strip()]
|
|
173
|
+
for statement in statements:
|
|
174
|
+
if statement:
|
|
175
|
+
self.connection.execute(statement)
|
|
176
|
+
self.connection.commit()
|
|
177
|
+
|
|
178
|
+
# Add role column if it doesn't exist
|
|
179
|
+
try:
|
|
180
|
+
self.connection.execute("""
|
|
181
|
+
ALTER TABLE user_profiles ADD COLUMN role TEXT DEFAULT 'user'
|
|
182
|
+
""")
|
|
183
|
+
self.connection.commit()
|
|
184
|
+
except Exception as e:
|
|
185
|
+
# Column might already exist, which is fine
|
|
186
|
+
if 'duplicate column name' not in str(e).lower():
|
|
187
|
+
raise
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
self.logger.error(f"Error initializing base schema: {e}")
|
|
191
|
+
raise
|
|
192
|
+
|
|
193
|
+
async def _ensure_required_columns(self) -> None:
|
|
194
|
+
"""Ensure all required columns exist in tables."""
|
|
195
|
+
try:
|
|
196
|
+
# Get current columns in conversation_contexts
|
|
197
|
+
cursor = self.connection.execute("PRAGMA table_info(conversation_contexts)")
|
|
198
|
+
columns = {col[1] for col in cursor.fetchall()}
|
|
199
|
+
|
|
200
|
+
# Add missing columns
|
|
201
|
+
required_columns = [
|
|
202
|
+
('mode', 'TEXT DEFAULT "active"'),
|
|
203
|
+
('mode_metadata', 'TEXT'),
|
|
204
|
+
('keywords', 'TEXT')
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
for column, definition in required_columns:
|
|
208
|
+
if column not in columns:
|
|
209
|
+
try:
|
|
210
|
+
self.connection.execute(
|
|
211
|
+
f"ALTER TABLE conversation_contexts ADD COLUMN {column} {definition}"
|
|
212
|
+
)
|
|
213
|
+
self.logger.debug(f"Added column {column} to conversation_contexts")
|
|
214
|
+
except Exception as e:
|
|
215
|
+
if "duplicate column name" not in str(e).lower():
|
|
216
|
+
raise
|
|
217
|
+
|
|
218
|
+
self.connection.commit()
|
|
219
|
+
|
|
220
|
+
except Exception as e:
|
|
221
|
+
self.logger.error(f"Error ensuring required columns: {e}")
|
|
222
|
+
raise
|
|
223
|
+
|
|
224
|
+
async def _initialize_remaining_schema(self) -> None:
|
|
225
|
+
"""Initialize remaining schema after base tables and columns are set up."""
|
|
226
|
+
try:
|
|
227
|
+
schema_path = Path(__file__).parent / 'schema.sql'
|
|
228
|
+
with open(schema_path, 'r') as f:
|
|
229
|
+
schema = f.read()
|
|
230
|
+
|
|
231
|
+
# Execute remaining schema statements
|
|
232
|
+
statements = [stmt.strip() for stmt in schema.split(';') if stmt.strip()]
|
|
233
|
+
for statement in statements:
|
|
234
|
+
if statement and (
|
|
235
|
+
statement.upper().startswith('CREATE TABLE') or
|
|
236
|
+
statement.upper().startswith('CREATE INDEX')
|
|
237
|
+
):
|
|
238
|
+
try:
|
|
239
|
+
self.connection.execute(statement)
|
|
240
|
+
except Exception as e:
|
|
241
|
+
if "already exists" not in str(e):
|
|
242
|
+
raise
|
|
243
|
+
|
|
244
|
+
self.connection.commit()
|
|
245
|
+
self.logger.info("Remaining schema initialized successfully")
|
|
246
|
+
|
|
247
|
+
except Exception as e:
|
|
248
|
+
self.logger.error(f"Error initializing remaining schema: {e}")
|
|
249
|
+
raise
|
|
250
|
+
|
|
251
|
+
def _split_sql_statements(self, sql: str) -> List[str]:
|
|
252
|
+
"""Carefully split SQL statements preserving triggers and functions."""
|
|
253
|
+
if not sql or not isinstance(sql, str):
|
|
254
|
+
self.logger.error("❌ Invalid SQL input for splitting")
|
|
255
|
+
raise ValueError("Invalid SQL input")
|
|
256
|
+
|
|
257
|
+
statements = []
|
|
258
|
+
current_statement = []
|
|
259
|
+
in_string = False
|
|
260
|
+
string_char = None
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
# Add check for None before processing
|
|
264
|
+
if sql is None:
|
|
265
|
+
return []
|
|
266
|
+
|
|
267
|
+
for line in sql.splitlines():
|
|
268
|
+
line = line.strip()
|
|
269
|
+
|
|
270
|
+
# Skip empty lines
|
|
271
|
+
if not line:
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
# Handle comments
|
|
275
|
+
if line.startswith('--'):
|
|
276
|
+
continue
|
|
277
|
+
|
|
278
|
+
# Remove inline comments while preserving strings
|
|
279
|
+
cleaned_line = ''
|
|
280
|
+
i = 0
|
|
281
|
+
while i < len(line):
|
|
282
|
+
if line[i:i+2] == '--' and not in_string:
|
|
283
|
+
break
|
|
284
|
+
elif line[i] in ["'", '"'] and (i == 0 or line[i-1] != '\\'):
|
|
285
|
+
if not in_string:
|
|
286
|
+
in_string = True
|
|
287
|
+
string_char = line[i]
|
|
288
|
+
elif line[i] == string_char:
|
|
289
|
+
in_string = False
|
|
290
|
+
cleaned_line += line[i]
|
|
291
|
+
i += 1
|
|
292
|
+
|
|
293
|
+
if cleaned_line:
|
|
294
|
+
current_statement.append(cleaned_line)
|
|
295
|
+
|
|
296
|
+
# Check if statement is complete
|
|
297
|
+
if cleaned_line.rstrip().endswith(';') and not in_string:
|
|
298
|
+
full_statement = ' '.join(current_statement).strip()
|
|
299
|
+
if full_statement:
|
|
300
|
+
statements.append(full_statement)
|
|
301
|
+
current_statement = []
|
|
302
|
+
|
|
303
|
+
# Handle any remaining statement
|
|
304
|
+
if current_statement:
|
|
305
|
+
full_statement = ' '.join(current_statement).strip()
|
|
306
|
+
if full_statement:
|
|
307
|
+
if not full_statement.endswith(';'):
|
|
308
|
+
full_statement += ';'
|
|
309
|
+
statements.append(full_statement)
|
|
310
|
+
|
|
311
|
+
self.logger.debug(f"Successfully split SQL into {len(statements)} statements")
|
|
312
|
+
return statements
|
|
313
|
+
|
|
314
|
+
except Exception as e:
|
|
315
|
+
self.logger.error(f"SQL splitting error: {str(e)}")
|
|
316
|
+
return []
|
|
317
|
+
|
|
318
|
+
async def begin_transaction(self) -> None:
|
|
319
|
+
"""Begin a new transaction.
|
|
320
|
+
|
|
321
|
+
SECURITY FIX: Set _in_transaction flag BEFORE calling execute() to prevent
|
|
322
|
+
race condition where another coroutine could auto-commit during the window
|
|
323
|
+
between execute() returning and flag being set.
|
|
324
|
+
"""
|
|
325
|
+
async with self._transaction_lock:
|
|
326
|
+
if self._in_transaction:
|
|
327
|
+
raise DatabaseError("Transaction already in progress")
|
|
328
|
+
# Set flag BEFORE execute to close race window
|
|
329
|
+
self._in_transaction = True
|
|
330
|
+
try:
|
|
331
|
+
await self.execute("BEGIN TRANSACTION")
|
|
332
|
+
except Exception:
|
|
333
|
+
# Rollback state on failure
|
|
334
|
+
self._in_transaction = False
|
|
335
|
+
raise
|
|
336
|
+
|
|
337
|
+
async def commit(self) -> None:
|
|
338
|
+
"""Commit the current transaction."""
|
|
339
|
+
async with self._transaction_lock:
|
|
340
|
+
if not self._in_transaction:
|
|
341
|
+
raise DatabaseError("No transaction in progress")
|
|
342
|
+
try:
|
|
343
|
+
await self.execute("COMMIT")
|
|
344
|
+
finally:
|
|
345
|
+
# Always clear flag, even on error (transaction is no longer active)
|
|
346
|
+
self._in_transaction = False
|
|
347
|
+
|
|
348
|
+
async def rollback(self) -> None:
|
|
349
|
+
"""Rollback the current transaction."""
|
|
350
|
+
async with self._transaction_lock:
|
|
351
|
+
if not self._in_transaction:
|
|
352
|
+
return # Silently ignore if no transaction
|
|
353
|
+
try:
|
|
354
|
+
await self.execute("ROLLBACK")
|
|
355
|
+
finally:
|
|
356
|
+
# Always clear flag, even on error
|
|
357
|
+
self._in_transaction = False
|
|
358
|
+
|
|
359
|
+
async def in_transaction(self) -> bool:
|
|
360
|
+
"""Check if currently in a transaction."""
|
|
361
|
+
return self._in_transaction
|
|
362
|
+
|
|
363
|
+
async def execute(self, query: str, params: Union[tuple, Dict[str, Any]] = ()) -> aiosqlite.Cursor:
|
|
364
|
+
"""Execute SQL query."""
|
|
365
|
+
try:
|
|
366
|
+
async with self._lock:
|
|
367
|
+
if not self.connection:
|
|
368
|
+
await self.connect()
|
|
369
|
+
|
|
370
|
+
# Debug logging
|
|
371
|
+
self.logger.debug(f"Execute query: {query}")
|
|
372
|
+
self.logger.debug(f"Params before processing: {params}")
|
|
373
|
+
|
|
374
|
+
# Handle JSON serialization in parameters
|
|
375
|
+
if isinstance(params, dict):
|
|
376
|
+
processed_params = {k: json.dumps(v) if isinstance(v, (dict, list)) else v
|
|
377
|
+
for k, v in params.items()}
|
|
378
|
+
else:
|
|
379
|
+
processed_params = tuple(json.dumps(p) if isinstance(p, (dict, list)) else p
|
|
380
|
+
for p in params)
|
|
381
|
+
|
|
382
|
+
# Debug logging
|
|
383
|
+
self.logger.debug(f"Processed params: {processed_params}")
|
|
384
|
+
|
|
385
|
+
cursor = self.connection.execute(query, processed_params)
|
|
386
|
+
|
|
387
|
+
# Only auto-commit if NOT in an explicit transaction AND not a transaction control statement
|
|
388
|
+
query_upper = query.strip().upper()
|
|
389
|
+
is_transaction_control = query_upper.startswith(('BEGIN', 'COMMIT', 'ROLLBACK'))
|
|
390
|
+
if not self._in_transaction and not is_transaction_control:
|
|
391
|
+
self.connection.commit()
|
|
392
|
+
|
|
393
|
+
return cursor
|
|
394
|
+
except Exception as e:
|
|
395
|
+
self.logger.error(f"Execute error: {e} | Query: {query} | Params: {params}")
|
|
396
|
+
self.logger.exception("Detailed error:")
|
|
397
|
+
raise
|
|
398
|
+
|
|
399
|
+
async def fetch_one(self, query: str, params: Union[tuple, Dict[str, Any]] = ()) -> Optional[Dict[str, Any]]:
|
|
400
|
+
"""Fetch a single row from the database."""
|
|
401
|
+
try:
|
|
402
|
+
async with self._lock:
|
|
403
|
+
if not self.connection:
|
|
404
|
+
await self.connect()
|
|
405
|
+
|
|
406
|
+
# Debug logging
|
|
407
|
+
self.logger.debug(f"Fetch one query: {query}")
|
|
408
|
+
self.logger.debug(f"Params before processing: {params}")
|
|
409
|
+
|
|
410
|
+
# Handle JSON serialization in parameters
|
|
411
|
+
if isinstance(params, dict):
|
|
412
|
+
processed_params = {k: json.dumps(v) if isinstance(v, (dict, list)) else v
|
|
413
|
+
for k, v in params.items()}
|
|
414
|
+
else:
|
|
415
|
+
processed_params = tuple(json.dumps(p) if isinstance(p, (dict, list)) else p
|
|
416
|
+
for p in params)
|
|
417
|
+
|
|
418
|
+
# Debug logging
|
|
419
|
+
self.logger.debug(f"Processed params: {processed_params}")
|
|
420
|
+
|
|
421
|
+
cursor = self.connection.execute(query, processed_params)
|
|
422
|
+
row = cursor.fetchone()
|
|
423
|
+
cursor.close()
|
|
424
|
+
|
|
425
|
+
if row:
|
|
426
|
+
columns = [description[0] for description in cursor.description]
|
|
427
|
+
# Handle potential JSON fields
|
|
428
|
+
result = {}
|
|
429
|
+
for col, val in zip(columns, row):
|
|
430
|
+
try:
|
|
431
|
+
if val is not None and isinstance(val, str) and col in ('preferences', 'metadata', 'messages', 'mode_metadata', 'verification_status'):
|
|
432
|
+
try:
|
|
433
|
+
result[col] = json.loads(val)
|
|
434
|
+
except json.JSONDecodeError:
|
|
435
|
+
result[col] = val
|
|
436
|
+
else:
|
|
437
|
+
# For NULL JSON fields, use empty dict/list based on field name
|
|
438
|
+
if val is None and col in ('preferences', 'metadata', 'messages', 'mode_metadata', 'verification_status'):
|
|
439
|
+
if col == 'messages':
|
|
440
|
+
result[col] = []
|
|
441
|
+
elif col == 'verification_status':
|
|
442
|
+
result[col] = {"email": False, "phone": False}
|
|
443
|
+
else:
|
|
444
|
+
result[col] = {}
|
|
445
|
+
else:
|
|
446
|
+
result[col] = val
|
|
447
|
+
except Exception as e:
|
|
448
|
+
self.logger.error(f"Error processing column {col} with value {val}: {e}")
|
|
449
|
+
result[col] = val
|
|
450
|
+
return result
|
|
451
|
+
return None
|
|
452
|
+
except Exception as e:
|
|
453
|
+
self.logger.error(f"Fetch one error: {e} | Query: {query} | Params: {params}")
|
|
454
|
+
self.logger.exception("Detailed error:")
|
|
455
|
+
raise
|
|
456
|
+
|
|
457
|
+
async def fetch_all(self, query: str, params: Union[tuple, Dict[str, Any]] = ()) -> List[Dict[str, Any]]:
|
|
458
|
+
"""Fetch all rows and return as list of dictionaries."""
|
|
459
|
+
try:
|
|
460
|
+
async with self._lock:
|
|
461
|
+
if not self.connection:
|
|
462
|
+
await self.connect()
|
|
463
|
+
|
|
464
|
+
# Debug logging
|
|
465
|
+
self.logger.debug(f"Fetch all query: {query}")
|
|
466
|
+
self.logger.debug(f"Params before processing: {params}")
|
|
467
|
+
|
|
468
|
+
# Handle JSON serialization in parameters
|
|
469
|
+
if isinstance(params, dict):
|
|
470
|
+
processed_params = {k: json.dumps(v) if isinstance(v, (dict, list)) else v
|
|
471
|
+
for k, v in params.items()}
|
|
472
|
+
else:
|
|
473
|
+
processed_params = tuple(json.dumps(p) if isinstance(p, (dict, list)) else p
|
|
474
|
+
for p in params)
|
|
475
|
+
|
|
476
|
+
# Debug logging
|
|
477
|
+
self.logger.debug(f"Processed params: {processed_params}")
|
|
478
|
+
|
|
479
|
+
cursor = self.connection.execute(query, processed_params)
|
|
480
|
+
rows = cursor.fetchall()
|
|
481
|
+
columns = [description[0] for description in cursor.description]
|
|
482
|
+
cursor.close()
|
|
483
|
+
|
|
484
|
+
# Debug logging
|
|
485
|
+
self.logger.debug(f"Raw rows: {rows}")
|
|
486
|
+
self.logger.debug(f"Columns: {columns}")
|
|
487
|
+
|
|
488
|
+
# Convert rows to dicts and handle JSON fields
|
|
489
|
+
result = []
|
|
490
|
+
for row in rows:
|
|
491
|
+
row_dict = {}
|
|
492
|
+
for col, val in zip(columns, row):
|
|
493
|
+
try:
|
|
494
|
+
# Handle potential JSON fields
|
|
495
|
+
if val is not None and isinstance(val, str) and col in ('preferences', 'metadata', 'messages', 'mode_metadata', 'verification_status'):
|
|
496
|
+
try:
|
|
497
|
+
row_dict[col] = json.loads(val)
|
|
498
|
+
except json.JSONDecodeError:
|
|
499
|
+
row_dict[col] = val
|
|
500
|
+
else:
|
|
501
|
+
# For NULL JSON fields, use empty dict/list based on field name
|
|
502
|
+
if val is None and col in ('preferences', 'metadata', 'messages', 'mode_metadata', 'verification_status'):
|
|
503
|
+
if col == 'messages':
|
|
504
|
+
row_dict[col] = []
|
|
505
|
+
elif col == 'verification_status':
|
|
506
|
+
row_dict[col] = {"email": False, "phone": False}
|
|
507
|
+
else:
|
|
508
|
+
row_dict[col] = {}
|
|
509
|
+
else:
|
|
510
|
+
row_dict[col] = val
|
|
511
|
+
except Exception as e:
|
|
512
|
+
self.logger.error(f"Error processing column {col} with value {val}: {e}")
|
|
513
|
+
row_dict[col] = val
|
|
514
|
+
result.append(row_dict)
|
|
515
|
+
|
|
516
|
+
# Debug logging
|
|
517
|
+
self.logger.debug(f"Processed result: {result}")
|
|
518
|
+
|
|
519
|
+
return result
|
|
520
|
+
except Exception as e:
|
|
521
|
+
self.logger.error(f"Fetch all error: {e} | Query: {query} | Params: {params}")
|
|
522
|
+
self.logger.exception("Detailed error:")
|
|
523
|
+
return []
|
|
524
|
+
|
|
525
|
+
async def executemany(self, query: str, param_list: List[Union[tuple, Dict[str, Any]]]) -> None:
|
|
526
|
+
"""Execute multiple queries with parameter lists."""
|
|
527
|
+
try:
|
|
528
|
+
async with self._lock:
|
|
529
|
+
if not self.connection:
|
|
530
|
+
await self.connect()
|
|
531
|
+
|
|
532
|
+
# Handle JSON serialization in parameters
|
|
533
|
+
processed_params = []
|
|
534
|
+
for params in param_list:
|
|
535
|
+
if isinstance(params, dict):
|
|
536
|
+
processed = {k: json.dumps(v) if isinstance(v, (dict, list)) else v
|
|
537
|
+
for k, v in params.items()}
|
|
538
|
+
else:
|
|
539
|
+
processed = tuple(json.dumps(p) if isinstance(p, (dict, list)) else p
|
|
540
|
+
for p in params)
|
|
541
|
+
processed_params.append(processed)
|
|
542
|
+
|
|
543
|
+
self.connection.executemany(query, processed_params)
|
|
544
|
+
self.connection.commit()
|
|
545
|
+
self.logger.debug(f"Executed multiple queries: {query}")
|
|
546
|
+
except Exception as e:
|
|
547
|
+
self.logger.error(f"Executemany error: {e} | Query: {query}")
|
|
548
|
+
raise
|
|
549
|
+
|
|
550
|
+
async def get_table_names(self) -> List[str]:
|
|
551
|
+
"""Get all table names from the database."""
|
|
552
|
+
query = "SELECT name FROM sqlite_master WHERE type='table';"
|
|
553
|
+
rows = await self.fetch_all(query)
|
|
554
|
+
return [row['name'] for row in rows]
|
|
555
|
+
|
|
556
|
+
async def __aenter__(self):
|
|
557
|
+
"""Async context manager entry."""
|
|
558
|
+
if not self.connection:
|
|
559
|
+
await self.connect()
|
|
560
|
+
return self
|
|
561
|
+
|
|
562
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
563
|
+
"""Async context manager exit."""
|
|
564
|
+
if exc_type:
|
|
565
|
+
self.connection.rollback()
|
|
566
|
+
else:
|
|
567
|
+
self.connection.commit()
|
|
568
|
+
await self.close()
|
|
569
|
+
|
|
570
|
+
async def transaction(self, statements: List[Tuple[str, tuple]]) -> None:
|
|
571
|
+
"""Execute multiple statements in a transaction.
|
|
572
|
+
|
|
573
|
+
Args:
|
|
574
|
+
statements: List of (query, params) tuples to execute
|
|
575
|
+
"""
|
|
576
|
+
try:
|
|
577
|
+
async with self._transaction_lock:
|
|
578
|
+
if not self.connection:
|
|
579
|
+
await self.connect()
|
|
580
|
+
|
|
581
|
+
# Start transaction
|
|
582
|
+
self.connection.execute("BEGIN TRANSACTION")
|
|
583
|
+
|
|
584
|
+
try:
|
|
585
|
+
# Execute all statements
|
|
586
|
+
for query, params in statements:
|
|
587
|
+
self.connection.execute(query, params)
|
|
588
|
+
|
|
589
|
+
# Commit if all successful
|
|
590
|
+
self.connection.commit()
|
|
591
|
+
|
|
592
|
+
except Exception as e:
|
|
593
|
+
# Rollback on any error
|
|
594
|
+
self.connection.rollback()
|
|
595
|
+
self.logger.error(f"Transaction failed, rolling back: {e}")
|
|
596
|
+
raise DatabaseError(f"Transaction failed: {e}")
|
|
597
|
+
|
|
598
|
+
except Exception as e:
|
|
599
|
+
self.logger.error(f"Error executing transaction: {e}")
|
|
600
|
+
raise DatabaseError(f"Failed to execute transaction: {e}")
|
|
601
|
+
|
|
602
|
+
def _auto_db_path() -> str:
|
|
603
|
+
"""Path to auto.db, anchored to the install/repo root (not the process CWD)."""
|
|
604
|
+
from pathlib import Path
|
|
605
|
+
return str(Path(__file__).resolve().parents[2] / "data" / "auto.db")
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def get_db_connection():
|
|
609
|
+
"""Get database connection."""
|
|
610
|
+
return sqlite3.connect(_auto_db_path())
|
|
611
|
+
|
|
612
|
+
def init_auto_tables():
|
|
613
|
+
"""Initialize auto-related tables."""
|
|
614
|
+
conn = get_db_connection()
|
|
615
|
+
cursor = conn.cursor()
|
|
616
|
+
|
|
617
|
+
# Create tables from schema
|
|
618
|
+
cursor.executescript('''
|
|
619
|
+
CREATE TABLE IF NOT EXISTS auto_knowledge (
|
|
620
|
+
id TEXT PRIMARY KEY,
|
|
621
|
+
topic TEXT NOT NULL,
|
|
622
|
+
content TEXT NOT NULL,
|
|
623
|
+
created_at TIMESTAMP,
|
|
624
|
+
updated_at TIMESTAMP
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
CREATE TABLE IF NOT EXISTS auto_interactions (
|
|
628
|
+
id TEXT PRIMARY KEY,
|
|
629
|
+
action TEXT NOT NULL,
|
|
630
|
+
result TEXT NOT NULL,
|
|
631
|
+
created_at TIMESTAMP
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
CREATE TABLE IF NOT EXISTS auto_cycles (
|
|
635
|
+
id TEXT PRIMARY KEY,
|
|
636
|
+
started_at TIMESTAMP,
|
|
637
|
+
ended_at TIMESTAMP,
|
|
638
|
+
status TEXT,
|
|
639
|
+
metrics JSON
|
|
640
|
+
);
|
|
641
|
+
''')
|
|
642
|
+
|
|
643
|
+
conn.commit()
|
|
644
|
+
conn.close()
|