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,89 @@
|
|
|
1
|
+
"""MCP validation-failure tracking — schema-injection policy.
|
|
2
|
+
|
|
3
|
+
Extracted from ToolCallTracker (PR11). Repeated MCP-tool validation failures
|
|
4
|
+
should escalate to injecting the tool's full schema to help the LLM. That is
|
|
5
|
+
MCP error *policy*, not tool-call ID lifecycle, so it lives here in tools/mcp/
|
|
6
|
+
rather than on the generic tracker. ToolCallTracker composes one of these and
|
|
7
|
+
delegates to it, preserving its public method names for existing callers.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from datetime import datetime, timedelta
|
|
14
|
+
from threading import RLock
|
|
15
|
+
from typing import Dict, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MCPValidationTracker:
|
|
19
|
+
"""Per-(server, tool) validation-failure counter with TTL expiry.
|
|
20
|
+
|
|
21
|
+
After ``failure_threshold`` failures within ``failure_ttl_minutes``, callers
|
|
22
|
+
should inject the full tool schema. A successful call clears the counter.
|
|
23
|
+
Self-contained and thread-safe (its own lock).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
logger: Optional[logging.Logger] = None,
|
|
29
|
+
failure_threshold: int = 2,
|
|
30
|
+
failure_ttl_minutes: int = 30,
|
|
31
|
+
) -> None:
|
|
32
|
+
self.logger = logger or logging.getLogger(__name__)
|
|
33
|
+
self._failures: Dict[str, int] = {}
|
|
34
|
+
self._timestamps: Dict[str, datetime] = {}
|
|
35
|
+
self._threshold = failure_threshold
|
|
36
|
+
self._ttl_minutes = failure_ttl_minutes
|
|
37
|
+
self._lock = RLock()
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def _key(server_name: str, tool_name: str) -> str:
|
|
41
|
+
return f"{server_name}:{tool_name}"
|
|
42
|
+
|
|
43
|
+
def track_failure(self, server_name: str, tool_name: str) -> int:
|
|
44
|
+
"""Record a validation failure; return the current count for this tool."""
|
|
45
|
+
with self._lock:
|
|
46
|
+
key = self._key(server_name, tool_name)
|
|
47
|
+
self._cleanup_expired()
|
|
48
|
+
self._failures[key] = self._failures.get(key, 0) + 1
|
|
49
|
+
self._timestamps[key] = datetime.now()
|
|
50
|
+
count = self._failures[key]
|
|
51
|
+
self.logger.warning(f"MCP validation failure #{count} for {key}")
|
|
52
|
+
return count
|
|
53
|
+
|
|
54
|
+
def should_inject_schema(self, server_name: str, tool_name: str) -> bool:
|
|
55
|
+
"""True once failures for this tool reach the threshold.
|
|
56
|
+
|
|
57
|
+
P2-12: TTL-clean on the READ path too. Previously only track_failure cleaned,
|
|
58
|
+
so once a (server, tool) reached the threshold it stayed there for the whole
|
|
59
|
+
session — the consumer blocks the action, so clear_failures (success-only) never
|
|
60
|
+
runs and, absent another MCP failure to trigger track_failure, the 30-min TTL
|
|
61
|
+
never fired. Cleaning here lets an expired failure record lapse.
|
|
62
|
+
"""
|
|
63
|
+
with self._lock:
|
|
64
|
+
self._cleanup_expired()
|
|
65
|
+
return self._failures.get(self._key(server_name, tool_name), 0) >= self._threshold
|
|
66
|
+
|
|
67
|
+
def clear_failures(self, server_name: str, tool_name: str) -> None:
|
|
68
|
+
"""Clear the failure count after a successful execution."""
|
|
69
|
+
with self._lock:
|
|
70
|
+
key = self._key(server_name, tool_name)
|
|
71
|
+
if key in self._failures:
|
|
72
|
+
del self._failures[key]
|
|
73
|
+
self._timestamps.pop(key, None)
|
|
74
|
+
self.logger.debug(f"Cleared MCP validation failures for {key}")
|
|
75
|
+
|
|
76
|
+
def reset(self) -> None:
|
|
77
|
+
"""Drop all tracked failures."""
|
|
78
|
+
with self._lock:
|
|
79
|
+
self._failures.clear()
|
|
80
|
+
self._timestamps.clear()
|
|
81
|
+
|
|
82
|
+
def _cleanup_expired(self) -> None:
|
|
83
|
+
"""Remove failures older than the TTL. Caller holds the lock."""
|
|
84
|
+
cutoff = datetime.now() - timedelta(minutes=self._ttl_minutes)
|
|
85
|
+
expired = [k for k, ts in self._timestamps.items() if ts < cutoff]
|
|
86
|
+
for key in expired:
|
|
87
|
+
self._failures.pop(key, None)
|
|
88
|
+
self._timestamps.pop(key, None)
|
|
89
|
+
self.logger.debug(f"Expired MCP validation failures for {key}")
|
tools/mcp/views.py
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"""Pydantic models for MCP service actions."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Any, Optional, List
|
|
4
|
+
from pydantic import BaseModel, Field, field_validator, ConfigDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MCPExecuteToolAction(BaseModel):
|
|
8
|
+
"""Parameters for executing an MCP tool."""
|
|
9
|
+
model_config = ConfigDict(extra='forbid')
|
|
10
|
+
|
|
11
|
+
server_name: str = Field(..., description="Name of the MCP server (e.g., 'anysite', 'ghost')")
|
|
12
|
+
tool_name: str = Field(..., description="Name of the tool to execute on the server")
|
|
13
|
+
# FIX (Jan 2026): Make arguments REQUIRED so LLMs cannot omit it
|
|
14
|
+
# Previously optional with default={}, but LLMs were completely omitting the field,
|
|
15
|
+
# causing all MCP tools to receive empty parameters.
|
|
16
|
+
# Making it required forces LLMs to include arguments={...} even if empty.
|
|
17
|
+
arguments: Dict[str, Any] = Field(
|
|
18
|
+
..., # Required - LLM MUST include this field
|
|
19
|
+
description=(
|
|
20
|
+
"REQUIRED: The parameters to pass to the target tool. "
|
|
21
|
+
"This dict is passed directly to the tool - PUT YOUR SEARCH PARAMETERS HERE! "
|
|
22
|
+
"Example: {'query': 'AI startups', 'count': 10} for search tools. "
|
|
23
|
+
"Example: {'keywords': 'CEO founder', 'count': 5} for LinkedIn search. "
|
|
24
|
+
"Use mcp_list_tools to see each tool's required parameters. "
|
|
25
|
+
"For tools that need no parameters, pass empty dict {}."
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
@field_validator('server_name')
|
|
30
|
+
@classmethod
|
|
31
|
+
def validate_server_name(cls, v):
|
|
32
|
+
"""Validate server name is not empty."""
|
|
33
|
+
if not v or not v.strip():
|
|
34
|
+
raise ValueError("server_name cannot be empty")
|
|
35
|
+
return v.strip()
|
|
36
|
+
|
|
37
|
+
@field_validator('tool_name')
|
|
38
|
+
@classmethod
|
|
39
|
+
def validate_tool_name(cls, v):
|
|
40
|
+
"""Validate tool name is not empty."""
|
|
41
|
+
if not v or not v.strip():
|
|
42
|
+
raise ValueError("tool_name cannot be empty")
|
|
43
|
+
return v.strip()
|
|
44
|
+
|
|
45
|
+
@field_validator('arguments')
|
|
46
|
+
@classmethod
|
|
47
|
+
def validate_arguments(cls, v):
|
|
48
|
+
"""Warn about empty arguments - most tools require them."""
|
|
49
|
+
if v is None:
|
|
50
|
+
v = {}
|
|
51
|
+
if not v:
|
|
52
|
+
# Log warning but allow empty for tools that don't need params
|
|
53
|
+
import logging
|
|
54
|
+
logging.getLogger(__name__).warning(
|
|
55
|
+
f"⚠️ Empty arguments provided for MCP tool. "
|
|
56
|
+
f"Most MCP tools require parameters - check mcp_list_tools for schema."
|
|
57
|
+
)
|
|
58
|
+
return v
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class MCPReadResourceAction(BaseModel):
|
|
62
|
+
"""Parameters for reading an MCP resource."""
|
|
63
|
+
model_config = ConfigDict(extra='forbid')
|
|
64
|
+
|
|
65
|
+
server_name: str = Field(..., description="Name of the MCP server")
|
|
66
|
+
resource_uri: str = Field(..., description="URI of the resource to read")
|
|
67
|
+
|
|
68
|
+
@field_validator('server_name')
|
|
69
|
+
@classmethod
|
|
70
|
+
def validate_server_name(cls, v):
|
|
71
|
+
"""Validate server name is not empty."""
|
|
72
|
+
if not v or not v.strip():
|
|
73
|
+
raise ValueError("server_name cannot be empty")
|
|
74
|
+
return v.strip()
|
|
75
|
+
|
|
76
|
+
@field_validator('resource_uri')
|
|
77
|
+
@classmethod
|
|
78
|
+
def validate_resource_uri(cls, v):
|
|
79
|
+
"""Validate resource URI is not empty."""
|
|
80
|
+
if not v or not v.strip():
|
|
81
|
+
raise ValueError("resource_uri cannot be empty")
|
|
82
|
+
return v.strip()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class MCPListToolsAction(BaseModel):
|
|
86
|
+
"""Parameters for listing MCP tools."""
|
|
87
|
+
model_config = ConfigDict(extra='ignore') # Allow extra fields but ignore them
|
|
88
|
+
|
|
89
|
+
server_name: Optional[str] = Field(None, description="Specific server name (optional, lists all if not provided)")
|
|
90
|
+
|
|
91
|
+
@field_validator('server_name')
|
|
92
|
+
@classmethod
|
|
93
|
+
def validate_server_name(cls, v):
|
|
94
|
+
"""Validate server name is not empty if provided."""
|
|
95
|
+
if v is not None and (not v or not v.strip()):
|
|
96
|
+
raise ValueError("server_name cannot be empty if provided")
|
|
97
|
+
return v.strip() if v else None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class MCPListResourcesAction(BaseModel):
|
|
101
|
+
"""Parameters for listing MCP resources."""
|
|
102
|
+
model_config = ConfigDict(extra='ignore') # Allow extra fields but ignore them
|
|
103
|
+
|
|
104
|
+
server_name: Optional[str] = Field(None, description="Specific server name (optional, lists all if not provided)")
|
|
105
|
+
|
|
106
|
+
@field_validator('server_name')
|
|
107
|
+
@classmethod
|
|
108
|
+
def validate_server_name(cls, v):
|
|
109
|
+
"""Validate server name is not empty if provided."""
|
|
110
|
+
if v is not None and (not v or not v.strip()):
|
|
111
|
+
raise ValueError("server_name cannot be empty if provided")
|
|
112
|
+
return v.strip() if v else None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class MCPListServersAction(BaseModel):
|
|
116
|
+
"""Parameters for listing MCP servers."""
|
|
117
|
+
model_config = ConfigDict(extra='ignore') # Allow extra fields but ignore them
|
|
118
|
+
|
|
119
|
+
include_disabled: bool = Field(False, description="Whether to include disabled servers")
|
|
120
|
+
include_details: bool = Field(True, description="Whether to include detailed server information")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class MCPServerStatusAction(BaseModel):
|
|
124
|
+
"""Parameters for getting MCP server status."""
|
|
125
|
+
model_config = ConfigDict(extra='forbid')
|
|
126
|
+
|
|
127
|
+
server_name: str = Field(..., description="Name of the MCP server")
|
|
128
|
+
|
|
129
|
+
@field_validator('server_name')
|
|
130
|
+
@classmethod
|
|
131
|
+
def validate_server_name(cls, v):
|
|
132
|
+
"""Validate server name is not empty."""
|
|
133
|
+
if not v or not v.strip():
|
|
134
|
+
raise ValueError("server_name cannot be empty")
|
|
135
|
+
return v.strip()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class MCPConnectServerAction(BaseModel):
|
|
139
|
+
"""Parameters for connecting to an MCP server."""
|
|
140
|
+
model_config = ConfigDict(extra='forbid')
|
|
141
|
+
|
|
142
|
+
server_name: str = Field(..., description="Name of the MCP server to connect")
|
|
143
|
+
|
|
144
|
+
@field_validator('server_name')
|
|
145
|
+
@classmethod
|
|
146
|
+
def validate_server_name(cls, v):
|
|
147
|
+
"""Validate server name is not empty."""
|
|
148
|
+
if not v or not v.strip():
|
|
149
|
+
raise ValueError("server_name cannot be empty")
|
|
150
|
+
return v.strip()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class MCPDisconnectServerAction(BaseModel):
|
|
154
|
+
"""Parameters for disconnecting from an MCP server."""
|
|
155
|
+
model_config = ConfigDict(extra='forbid')
|
|
156
|
+
|
|
157
|
+
server_name: str = Field(..., description="Name of the MCP server to disconnect")
|
|
158
|
+
|
|
159
|
+
@field_validator('server_name')
|
|
160
|
+
@classmethod
|
|
161
|
+
def validate_server_name(cls, v):
|
|
162
|
+
"""Validate server name is not empty."""
|
|
163
|
+
if not v or not v.strip():
|
|
164
|
+
raise ValueError("server_name cannot be empty")
|
|
165
|
+
return v.strip()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class MCPReloadServerAction(BaseModel):
|
|
169
|
+
"""Parameters for reloading an MCP server connection."""
|
|
170
|
+
model_config = ConfigDict(extra='forbid')
|
|
171
|
+
|
|
172
|
+
server_name: str = Field(..., description="Name of the MCP server to reload")
|
|
173
|
+
|
|
174
|
+
@field_validator('server_name')
|
|
175
|
+
@classmethod
|
|
176
|
+
def validate_server_name(cls, v):
|
|
177
|
+
"""Validate server name is not empty."""
|
|
178
|
+
if not v or not v.strip():
|
|
179
|
+
raise ValueError("server_name cannot be empty")
|
|
180
|
+
return v.strip()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class MCPGetCapabilitiesAction(BaseModel):
|
|
184
|
+
"""Parameters for getting MCP server capabilities."""
|
|
185
|
+
model_config = ConfigDict(extra='forbid')
|
|
186
|
+
|
|
187
|
+
server_name: str = Field(..., description="Name of the MCP server")
|
|
188
|
+
|
|
189
|
+
@field_validator('server_name')
|
|
190
|
+
@classmethod
|
|
191
|
+
def validate_server_name(cls, v):
|
|
192
|
+
"""Validate server name is not empty."""
|
|
193
|
+
if not v or not v.strip():
|
|
194
|
+
raise ValueError("server_name cannot be empty")
|
|
195
|
+
return v.strip()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class MCPSubscribeResourceAction(BaseModel):
|
|
199
|
+
"""Parameters for subscribing to MCP resource updates."""
|
|
200
|
+
model_config = ConfigDict(extra='forbid')
|
|
201
|
+
|
|
202
|
+
server_name: str = Field(..., description="Name of the MCP server")
|
|
203
|
+
resource_uri: str = Field(..., description="URI of the resource to subscribe to")
|
|
204
|
+
|
|
205
|
+
@field_validator('server_name')
|
|
206
|
+
@classmethod
|
|
207
|
+
def validate_server_name(cls, v):
|
|
208
|
+
"""Validate server name is not empty."""
|
|
209
|
+
if not v or not v.strip():
|
|
210
|
+
raise ValueError("server_name cannot be empty")
|
|
211
|
+
return v.strip()
|
|
212
|
+
|
|
213
|
+
@field_validator('resource_uri')
|
|
214
|
+
@classmethod
|
|
215
|
+
def validate_resource_uri(cls, v):
|
|
216
|
+
"""Validate resource URI is not empty."""
|
|
217
|
+
if not v or not v.strip():
|
|
218
|
+
raise ValueError("resource_uri cannot be empty")
|
|
219
|
+
return v.strip()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class MCPUnsubscribeResourceAction(BaseModel):
|
|
223
|
+
"""Parameters for unsubscribing from MCP resource updates."""
|
|
224
|
+
model_config = ConfigDict(extra='forbid')
|
|
225
|
+
|
|
226
|
+
server_name: str = Field(..., description="Name of the MCP server")
|
|
227
|
+
resource_uri: str = Field(..., description="URI of the resource to unsubscribe from")
|
|
228
|
+
|
|
229
|
+
@field_validator('server_name')
|
|
230
|
+
@classmethod
|
|
231
|
+
def validate_server_name(cls, v):
|
|
232
|
+
"""Validate server name is not empty."""
|
|
233
|
+
if not v or not v.strip():
|
|
234
|
+
raise ValueError("server_name cannot be empty")
|
|
235
|
+
return v.strip()
|
|
236
|
+
|
|
237
|
+
@field_validator('resource_uri')
|
|
238
|
+
@classmethod
|
|
239
|
+
def validate_resource_uri(cls, v):
|
|
240
|
+
"""Validate resource URI is not empty."""
|
|
241
|
+
if not v or not v.strip():
|
|
242
|
+
raise ValueError("resource_uri cannot be empty")
|
|
243
|
+
return v.strip()
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class MCPHealthCheckAction(BaseModel):
|
|
247
|
+
"""Parameters for performing MCP server health check."""
|
|
248
|
+
model_config = ConfigDict(extra='ignore')
|
|
249
|
+
|
|
250
|
+
server_name: Optional[str] = Field(None, description="Specific server name (optional, checks all if not provided)")
|
|
251
|
+
|
|
252
|
+
@field_validator('server_name')
|
|
253
|
+
@classmethod
|
|
254
|
+
def validate_server_name(cls, v):
|
|
255
|
+
"""Validate server name is not empty if provided."""
|
|
256
|
+
if v is not None and (not v or not v.strip()):
|
|
257
|
+
raise ValueError("server_name cannot be empty if provided")
|
|
258
|
+
return v.strip() if v else None
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# Response models for better type safety and documentation
|
|
262
|
+
class MCPToolInfo(BaseModel):
|
|
263
|
+
"""Information about an MCP tool."""
|
|
264
|
+
|
|
265
|
+
name: str
|
|
266
|
+
description: str
|
|
267
|
+
server_name: str
|
|
268
|
+
input_schema: Dict[str, Any]
|
|
269
|
+
|
|
270
|
+
def __str__(self) -> str:
|
|
271
|
+
"""Format tool info for LLM visibility with schema details."""
|
|
272
|
+
# Extract required parameters from schema
|
|
273
|
+
props = self.input_schema.get('properties', {})
|
|
274
|
+
required = self.input_schema.get('required', [])
|
|
275
|
+
|
|
276
|
+
# Build parameter summary
|
|
277
|
+
params = []
|
|
278
|
+
for param_name, param_schema in props.items():
|
|
279
|
+
param_type = param_schema.get('type', 'any')
|
|
280
|
+
is_required = param_name in required
|
|
281
|
+
req_marker = '(required)' if is_required else '(optional)'
|
|
282
|
+
param_desc = param_schema.get('description', '')
|
|
283
|
+
params.append(f" - {param_name}: {param_type} {req_marker} - {param_desc}")
|
|
284
|
+
|
|
285
|
+
params_str = '\n'.join(params) if params else ' (no parameters)'
|
|
286
|
+
|
|
287
|
+
return f"""
|
|
288
|
+
Tool: {self.name}
|
|
289
|
+
Server: {self.server_name}
|
|
290
|
+
Description: {self.description}
|
|
291
|
+
Parameters:
|
|
292
|
+
{params_str}
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class MCPResourceInfo(BaseModel):
|
|
297
|
+
"""Information about an MCP resource."""
|
|
298
|
+
|
|
299
|
+
uri: str
|
|
300
|
+
name: str
|
|
301
|
+
server_name: str
|
|
302
|
+
description: Optional[str] = None
|
|
303
|
+
mime_type: Optional[str] = None
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class MCPServerInfo(BaseModel):
|
|
307
|
+
"""Information about an MCP server."""
|
|
308
|
+
|
|
309
|
+
name: str
|
|
310
|
+
status: str
|
|
311
|
+
type: str
|
|
312
|
+
enabled: bool
|
|
313
|
+
connected_at: Optional[float] = None
|
|
314
|
+
last_error: Optional[str] = None
|
|
315
|
+
retry_count: int = 0
|
|
316
|
+
tools_count: int = 0
|
|
317
|
+
resources_count: int = 0
|
|
318
|
+
capabilities: Dict[str, Any] = Field(default_factory=dict)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class MCPExecutionResult(BaseModel):
|
|
322
|
+
"""Result of MCP tool execution."""
|
|
323
|
+
|
|
324
|
+
success: bool
|
|
325
|
+
result: Any = None
|
|
326
|
+
error: Optional[str] = None
|
|
327
|
+
execution_time: Optional[float] = None
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class MCPResourceContent(BaseModel):
|
|
331
|
+
"""Content of an MCP resource."""
|
|
332
|
+
|
|
333
|
+
uri: str
|
|
334
|
+
content: Any
|
|
335
|
+
mime_type: Optional[str] = None
|
|
336
|
+
size: Optional[int] = None
|
|
337
|
+
last_modified: Optional[float] = None
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class MCPHealthStatus(BaseModel):
|
|
341
|
+
"""Health status of MCP servers."""
|
|
342
|
+
|
|
343
|
+
healthy_servers: List[str] = Field(default_factory=list)
|
|
344
|
+
unhealthy_servers: List[str] = Field(default_factory=list)
|
|
345
|
+
total_servers: int = 0
|
|
346
|
+
overall_health: str = "unknown" # "healthy", "degraded", "unhealthy"
|
tools/oauth/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Unified OAuth manager (Item 4 — minimal WS-G).
|
|
2
|
+
|
|
3
|
+
Library-only: an ``OAuthProvider`` seam + ``OAuthManager`` registry on the existing
|
|
4
|
+
Fernet token store, plus one ``GenericOAuth2Provider``. No existing tool is migrated
|
|
5
|
+
(deferred). Mint/store(encrypted)/refresh a token per user through the manager.
|
|
6
|
+
"""
|
|
7
|
+
from tools.oauth.provider import OAuthError, OAuthProvider, OAuthToken
|
|
8
|
+
from tools.oauth.manager import OAuthManager
|
|
9
|
+
from tools.oauth.providers.generic_oauth2 import GenericOAuth2Provider
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"OAuthError",
|
|
13
|
+
"OAuthProvider",
|
|
14
|
+
"OAuthToken",
|
|
15
|
+
"OAuthManager",
|
|
16
|
+
"GenericOAuth2Provider",
|
|
17
|
+
]
|
tools/oauth/manager.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""OAuth manager (Item 4 — WS-G1, trimmed).
|
|
2
|
+
|
|
3
|
+
Holds registered ``OAuthProvider`` instances and a per-``(user_id, provider)`` token
|
|
4
|
+
store encrypted with the EXISTING Fernet helper (``tools/mcp/security.py::
|
|
5
|
+
MCPEncryption``). ``get_token`` returns a cached valid token, auto-refreshing on
|
|
6
|
+
expiry. Library-only — no tool migration; mirrors ``MemoryProviderRegistry``'s shape.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Dict, MutableMapping, Optional, Tuple
|
|
11
|
+
|
|
12
|
+
from tools.oauth.provider import OAuthError, OAuthProvider, OAuthToken
|
|
13
|
+
|
|
14
|
+
Key = Tuple[str, str]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class OAuthManager:
|
|
18
|
+
"""Provider registry + encrypted token store keyed by ``(user_id, provider)``."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, encryption: Any = None, store: Optional[MutableMapping[Key, bytes]] = None) -> None:
|
|
21
|
+
self._providers: Dict[str, OAuthProvider] = {}
|
|
22
|
+
# Encrypted-at-rest store: (user_id, provider) -> Fernet ciphertext bytes.
|
|
23
|
+
self._store: MutableMapping[Key, bytes] = store if store is not None else {}
|
|
24
|
+
self._encryption = encryption or self._default_encryption()
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def _default_encryption():
|
|
28
|
+
from tools.mcp.security import MCPEncryption
|
|
29
|
+
return MCPEncryption()
|
|
30
|
+
|
|
31
|
+
# -- provider registry ----------------------------------------------------
|
|
32
|
+
|
|
33
|
+
def register(self, provider: OAuthProvider) -> OAuthProvider:
|
|
34
|
+
self._providers[provider.name] = provider
|
|
35
|
+
return provider
|
|
36
|
+
|
|
37
|
+
def get_provider(self, name: str) -> OAuthProvider:
|
|
38
|
+
prov = self._providers.get(name)
|
|
39
|
+
if prov is None:
|
|
40
|
+
raise OAuthError(
|
|
41
|
+
f"unknown oauth provider '{name}' (known: {sorted(self._providers)})"
|
|
42
|
+
)
|
|
43
|
+
return prov
|
|
44
|
+
|
|
45
|
+
# -- encrypted token store ------------------------------------------------
|
|
46
|
+
|
|
47
|
+
def store_token(self, user_id: str, provider: str, token: OAuthToken) -> None:
|
|
48
|
+
self._store[(user_id, provider)] = self._encryption.encrypt_dict(token.to_dict())
|
|
49
|
+
|
|
50
|
+
def load_token(self, user_id: str, provider: str) -> Optional[OAuthToken]:
|
|
51
|
+
blob = self._store.get((user_id, provider))
|
|
52
|
+
if not blob:
|
|
53
|
+
return None
|
|
54
|
+
return OAuthToken.from_dict(self._encryption.decrypt_dict(blob))
|
|
55
|
+
|
|
56
|
+
def has_token(self, user_id: str, provider: str) -> bool:
|
|
57
|
+
return (user_id, provider) in self._store
|
|
58
|
+
|
|
59
|
+
# -- main entrypoint ------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
async def get_token(self, user_id: str, provider: str) -> OAuthToken:
|
|
62
|
+
"""Return a valid token for ``(user_id, provider)``, refreshing if expired."""
|
|
63
|
+
prov = self.get_provider(provider)
|
|
64
|
+
token = self.load_token(user_id, provider)
|
|
65
|
+
if token is None:
|
|
66
|
+
raise OAuthError(f"no token stored for user='{user_id}' provider='{provider}'")
|
|
67
|
+
if prov.valid_token(token):
|
|
68
|
+
return token
|
|
69
|
+
if not token.refresh_token:
|
|
70
|
+
raise OAuthError(
|
|
71
|
+
f"token expired and no refresh_token for user='{user_id}' provider='{provider}'"
|
|
72
|
+
)
|
|
73
|
+
refreshed = await prov.refresh(token)
|
|
74
|
+
self.store_token(user_id, provider, refreshed)
|
|
75
|
+
return refreshed
|
tools/oauth/provider.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""OAuth provider seam (Item 4 — WS-G1, trimmed).
|
|
2
|
+
|
|
3
|
+
An ``OAuthProvider`` knows how to build an authorize URL, exchange an auth code for
|
|
4
|
+
tokens, and refresh them. ``OAuthToken`` is the normalised token shape stored
|
|
5
|
+
(encrypted) by the ``OAuthManager``.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import time
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from dataclasses import asdict, dataclass, field
|
|
12
|
+
from typing import Any, Dict, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class OAuthError(RuntimeError):
|
|
16
|
+
"""Raised for unknown-provider / missing-token / refresh failures."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class OAuthToken:
|
|
21
|
+
"""A normalised OAuth2 token set."""
|
|
22
|
+
access_token: str
|
|
23
|
+
refresh_token: Optional[str] = None
|
|
24
|
+
expires_at: Optional[float] = None # epoch seconds; None = non-expiring
|
|
25
|
+
token_type: str = "Bearer"
|
|
26
|
+
scope: Optional[str] = None
|
|
27
|
+
extra: Dict[str, Any] = field(default_factory=dict)
|
|
28
|
+
|
|
29
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
30
|
+
return asdict(self)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def from_dict(cls, d: Dict[str, Any]) -> "OAuthToken":
|
|
34
|
+
d = dict(d or {})
|
|
35
|
+
known = {f for f in cls.__dataclass_fields__} # type: ignore[attr-defined]
|
|
36
|
+
extra = {k: d.pop(k) for k in list(d) if k not in known}
|
|
37
|
+
token = cls(**{k: v for k, v in d.items() if k in known})
|
|
38
|
+
if extra:
|
|
39
|
+
token.extra.update(extra)
|
|
40
|
+
return token
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class OAuthProvider(ABC):
|
|
44
|
+
"""Auth-code + refresh-token OAuth2 provider."""
|
|
45
|
+
|
|
46
|
+
name: str = "oauth"
|
|
47
|
+
|
|
48
|
+
#: refresh this many seconds before actual expiry
|
|
49
|
+
leeway_sec: float = 60.0
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def authorize_url(self, *, state: Optional[str] = None, redirect_uri: Optional[str] = None) -> str:
|
|
53
|
+
"""Build the user-facing authorization URL."""
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
async def exchange_code(self, code: str, *, redirect_uri: Optional[str] = None) -> OAuthToken:
|
|
57
|
+
"""Exchange an authorization code for a token set."""
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
async def refresh(self, token: OAuthToken) -> OAuthToken:
|
|
61
|
+
"""Exchange a refresh token for a fresh token set."""
|
|
62
|
+
|
|
63
|
+
def valid_token(self, token: Optional[OAuthToken]) -> bool:
|
|
64
|
+
"""True if ``token`` is present and not within ``leeway_sec`` of expiry."""
|
|
65
|
+
if not token or not token.access_token:
|
|
66
|
+
return False
|
|
67
|
+
if token.expires_at is None:
|
|
68
|
+
return True
|
|
69
|
+
return time.time() < (token.expires_at - self.leeway_sec)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""OAuth providers (Item 4). Only the generic auth-code provider ships."""
|