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,471 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Collab.Land integration service.
|
|
4
|
+
|
|
5
|
+
This service provides helper methods and bot actions to:
|
|
6
|
+
1. Check whether a given blockchain address holds the required token(s) using the Collab.Land
|
|
7
|
+
`access-control/check-roles` endpoint.
|
|
8
|
+
2. Persist wallet → membership status inside the configured `database_manager`.
|
|
9
|
+
3. Allow administrators to trigger a background refresh to keep the local cache in-sync
|
|
10
|
+
with on-chain reality (users that dumped the token are revoked, new holders are granted).
|
|
11
|
+
|
|
12
|
+
The implementation conforms to the project-wide service conventions (inherits from
|
|
13
|
+
`BaseService`, defines `@action` decorated public endpoints, handles dependency
|
|
14
|
+
injection, proper life-cycle management, rate-limit handling, etc.).
|
|
15
|
+
|
|
16
|
+
NOTE: Collab.Land exposes a huge REST surface. For the initial iteration we only
|
|
17
|
+
consume the token-gating endpoint:
|
|
18
|
+
POST https://api.collab.land/access-control/check-roles
|
|
19
|
+
See https://dev.collab.land/docs/tutorials/token-gating-tutorial for details.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
import logging
|
|
24
|
+
import time
|
|
25
|
+
from typing import Any, Dict, List, Optional
|
|
26
|
+
|
|
27
|
+
import aiohttp
|
|
28
|
+
from pydantic import BaseModel, Field
|
|
29
|
+
|
|
30
|
+
from core.config import BotConfig
|
|
31
|
+
from core.exceptions import APIError, ConfigurationError, ToolError
|
|
32
|
+
from tools.base_tool import BaseTool, ToolStatus
|
|
33
|
+
|
|
34
|
+
__all__ = ["CollabLandTool"]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
# Pydantic models for public actions
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CheckTokenParams(BaseModel):
|
|
43
|
+
"""Action parameters for `collabland_check_token`."""
|
|
44
|
+
|
|
45
|
+
address: str = Field(..., description="EVM compatible wallet address to check")
|
|
46
|
+
rules: Optional[List[Dict[str, Any]]] = Field(
|
|
47
|
+
None,
|
|
48
|
+
description=(
|
|
49
|
+
"Optional explicit Token-Gating Rules overriding the defaults defined in "
|
|
50
|
+
"bot configuration (see `collabland_rules`)."
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class AdminRefreshParams(BaseModel):
|
|
56
|
+
"""Parameters for the admin batch refresh action."""
|
|
57
|
+
|
|
58
|
+
confirm: bool = Field(
|
|
59
|
+
False,
|
|
60
|
+
description="Extra safety switch. Must be `true` to actually run the refresh.",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# Service implementation
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class CollabLandTool(BaseTool):
|
|
70
|
+
"""Integration with Collab.Land token-gating endpoints."""
|
|
71
|
+
|
|
72
|
+
# ---- Dependencies -----------------------------------------------------
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def required_services(self) -> Dict[str, str]:
|
|
76
|
+
return {
|
|
77
|
+
"rate_limit_manager": "Rate limit management",
|
|
78
|
+
"database_manager": "Database persistence",
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def optional_services(self) -> Dict[str, str]:
|
|
83
|
+
return {
|
|
84
|
+
"cache_manager": "Optional cache layer",
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# ---- Configuration ----------------------------------------------------
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def required_config(self) -> Dict[str, str]:
|
|
91
|
+
return {
|
|
92
|
+
"collabland_api_key": "Collab.Land API key",
|
|
93
|
+
"collabland_rules": "Default Collab.Land token-gating rules (JSON list)",
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# ---- Lifecycle --------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
def __init__(self, name: str, config: BotConfig, container: Optional[Any] = None):
|
|
99
|
+
super().__init__(name=name, config=config, container=container)
|
|
100
|
+
|
|
101
|
+
self._session: Optional[aiohttp.ClientSession] = None
|
|
102
|
+
self.base_url = getattr(config, "collabland_api_url", "https://api.collab.land")
|
|
103
|
+
self._rules: List[Dict[str, Any]] = getattr(config, "collabland_rules", []) or []
|
|
104
|
+
|
|
105
|
+
# Get all CollabLand configuration from BotConfig
|
|
106
|
+
self._api_key = getattr(config, "collabland_api_key", "")
|
|
107
|
+
self._client_id = getattr(config, "collabland_id", "")
|
|
108
|
+
self._client_secret = getattr(config, "collabland_secret", "")
|
|
109
|
+
|
|
110
|
+
# Handle compound API key format (clientId.secret)
|
|
111
|
+
if self._api_key and '.' in self._api_key and not self._client_id:
|
|
112
|
+
try:
|
|
113
|
+
parts = self._api_key.split('.', 1)
|
|
114
|
+
if len(parts) == 2:
|
|
115
|
+
self._client_id = parts[0]
|
|
116
|
+
self._client_secret = parts[1]
|
|
117
|
+
self.logger.info(f"Parsed compound API key: client_id={self._client_id[:5]}***, secret={self._client_secret[:5]}***")
|
|
118
|
+
except Exception as e:
|
|
119
|
+
self.logger.warning(f"Failed to parse compound API key: {e}")
|
|
120
|
+
|
|
121
|
+
# Determine if service is properly configured
|
|
122
|
+
self._enabled = bool(self._api_key) # Only enable if we have an API key
|
|
123
|
+
|
|
124
|
+
# Log the setup clearly
|
|
125
|
+
if not self._api_key:
|
|
126
|
+
self.logger.error("CRITICAL: Collab.Land API key missing – service disabled")
|
|
127
|
+
self._enabled = False
|
|
128
|
+
else:
|
|
129
|
+
self.logger.info(f"CollabLand service configured:")
|
|
130
|
+
self.logger.info(f" - Base URL: {self.base_url}")
|
|
131
|
+
self.logger.info(f" - API Key: {self._api_key[:5]}*** (length: {len(self._api_key)})")
|
|
132
|
+
if self._client_id:
|
|
133
|
+
self.logger.info(f" - Client ID: {self._client_id}")
|
|
134
|
+
if self._client_secret:
|
|
135
|
+
self.logger.info(f" - Client Secret: {self._client_secret[:5]}***")
|
|
136
|
+
self._enabled = True
|
|
137
|
+
|
|
138
|
+
# Check contract address format if provided
|
|
139
|
+
contract_address = getattr(config, "den_token_contract_address", None)
|
|
140
|
+
if contract_address:
|
|
141
|
+
if not contract_address.startswith("0x") or len(contract_address) != 42:
|
|
142
|
+
self.logger.warning(f"Invalid ERC-721 contract address format: {contract_address}")
|
|
143
|
+
else:
|
|
144
|
+
self.logger.info(f"Using contract address: {contract_address}")
|
|
145
|
+
else:
|
|
146
|
+
self.logger.warning("Token contract address not configured - token verification may be unreliable")
|
|
147
|
+
|
|
148
|
+
# Add default ERC721 rule if no rules configured
|
|
149
|
+
if not self._rules and contract_address:
|
|
150
|
+
self.logger.info("No CollabLand rules provided, creating default ERC721 rule")
|
|
151
|
+
self._rules = [{
|
|
152
|
+
"type": "ERC721",
|
|
153
|
+
"chainId": 1, # Ethereum mainnet
|
|
154
|
+
"contractAddress": contract_address
|
|
155
|
+
}]
|
|
156
|
+
self.logger.info(f"Created default rule: {self._rules}")
|
|
157
|
+
|
|
158
|
+
async def _initialize(self) -> None:
|
|
159
|
+
"""Create the shared aiohttp session and test connectivity."""
|
|
160
|
+
# Ensure decorated actions are collected
|
|
161
|
+
await super()._initialize()
|
|
162
|
+
|
|
163
|
+
# Skip if not enabled
|
|
164
|
+
if not self._enabled:
|
|
165
|
+
self._status = ToolStatus.DISABLED
|
|
166
|
+
self.logger.warning("CollabLand service disabled due to missing API key")
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
# Set up HTTP session
|
|
170
|
+
headers = {
|
|
171
|
+
"X-API-Key": self._api_key, # Use the full API key as provided
|
|
172
|
+
"Content-Type": "application/json",
|
|
173
|
+
"Accept": "application/json",
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# Log headers for debugging (mask sensitive data)
|
|
177
|
+
self.logger.debug(f"Setting up HTTP session with headers:")
|
|
178
|
+
self.logger.debug(f" - X-API-Key: {self._api_key[:10]}*** (full length: {len(self._api_key)})")
|
|
179
|
+
self.logger.debug(f" - Content-Type: application/json")
|
|
180
|
+
self.logger.debug(f" - Accept: application/json")
|
|
181
|
+
|
|
182
|
+
# Create session
|
|
183
|
+
timeout = aiohttp.ClientTimeout(total=30)
|
|
184
|
+
self._session = aiohttp.ClientSession(headers=headers, timeout=timeout)
|
|
185
|
+
|
|
186
|
+
# Test connectivity
|
|
187
|
+
try:
|
|
188
|
+
# Simple test to check if API is reachable
|
|
189
|
+
async with self._session.get(f"{self.base_url}/health") as response:
|
|
190
|
+
if response.status == 200:
|
|
191
|
+
self._status = ToolStatus.HEALTHY
|
|
192
|
+
self.logger.info("CollabLand API connectivity verified")
|
|
193
|
+
else:
|
|
194
|
+
self._status = ToolStatus.DEGRADED
|
|
195
|
+
self.logger.warning(f"CollabLand API returned status {response.status}")
|
|
196
|
+
except Exception as e:
|
|
197
|
+
self._status = ToolStatus.DEGRADED
|
|
198
|
+
self.logger.error(f"Failed to connect to CollabLand API: {e}")
|
|
199
|
+
# Don't disable the service, as it may recover later
|
|
200
|
+
|
|
201
|
+
async def _cleanup(self) -> None:
|
|
202
|
+
if self._session and not self._session.closed:
|
|
203
|
+
await self._session.close()
|
|
204
|
+
self._session = None
|
|
205
|
+
self.logger.debug("CollabLand HTTP session closed")
|
|
206
|
+
|
|
207
|
+
# ---------------------------------------------------------------------
|
|
208
|
+
# Internal helpers
|
|
209
|
+
# ---------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
async def _check_roles(self, address: str, rules: List[Dict[str, Any]]) -> tuple[bool, Optional[str]]:
|
|
212
|
+
"""Return (has_token, token_id) if the address fulfils all provided rules."""
|
|
213
|
+
# If service is disabled or not working, return False - no auto-pass
|
|
214
|
+
if not self._enabled:
|
|
215
|
+
self.logger.warning(f"CollabLand service disabled - token verification failed for wallet {address[:6]}...{address[-4:]}")
|
|
216
|
+
return False, None
|
|
217
|
+
|
|
218
|
+
# Ensure session exists
|
|
219
|
+
if not self._session:
|
|
220
|
+
self.logger.error("HTTP session not initialized - cannot verify token")
|
|
221
|
+
return False, None
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
# Prepare request payload
|
|
225
|
+
payload = {
|
|
226
|
+
"account": address,
|
|
227
|
+
"rules": rules,
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# Log the full request details for debugging
|
|
231
|
+
self.logger.info(f"CollabLand API request: POST {self.base_url}/access-control/check-roles")
|
|
232
|
+
self.logger.info(f"Request payload: {payload}")
|
|
233
|
+
self.logger.debug(f"Request headers: X-API-Key: {self._api_key[:10]}*** (full: {len(self._api_key)} chars)")
|
|
234
|
+
self.logger.debug(f"Full API key for debugging: {self._api_key}") # Temporary debug - remove in production
|
|
235
|
+
|
|
236
|
+
# Make API call to CollabLand
|
|
237
|
+
url = f"{self.base_url}/access-control/check-roles"
|
|
238
|
+
self.logger.info(f"Sending CollabLand API request to {url} for wallet {address[:6]}...{address[-4:]}")
|
|
239
|
+
|
|
240
|
+
start_time = time.time()
|
|
241
|
+
async with self._session.post(url, json=payload) as response:
|
|
242
|
+
# Calculate response time
|
|
243
|
+
response_time = round((time.time() - start_time) * 1000)
|
|
244
|
+
|
|
245
|
+
# Log response status and headers at INFO level
|
|
246
|
+
self.logger.info(f"CollabLand API response: status={response.status}, time={response_time}ms")
|
|
247
|
+
self.logger.debug(f"CollabLand API response headers: {dict(response.headers)}")
|
|
248
|
+
|
|
249
|
+
# Get response body for detailed logging
|
|
250
|
+
response_body = await response.text()
|
|
251
|
+
self.logger.debug(f"CollabLand API response body: {response_body}")
|
|
252
|
+
|
|
253
|
+
# Parse response
|
|
254
|
+
if response.status == 200:
|
|
255
|
+
try:
|
|
256
|
+
data = await response.json()
|
|
257
|
+
self.logger.info(f"CollabLand API returned JSON response: {data}")
|
|
258
|
+
|
|
259
|
+
# Extract relevant information
|
|
260
|
+
has_token = data.get("hasAccess", False)
|
|
261
|
+
|
|
262
|
+
# Try to extract token ID if available
|
|
263
|
+
token_id = None
|
|
264
|
+
if has_token:
|
|
265
|
+
# Access token details from response
|
|
266
|
+
token_details = data.get("tokenBalances", [])
|
|
267
|
+
if token_details:
|
|
268
|
+
# Take the first token ID if available
|
|
269
|
+
first_token = token_details[0]
|
|
270
|
+
token_id = first_token.get("tokenId")
|
|
271
|
+
self.logger.info(f"Found token ID: {token_id}")
|
|
272
|
+
|
|
273
|
+
return has_token, token_id
|
|
274
|
+
except Exception as parse_error:
|
|
275
|
+
self.logger.error(f"Error parsing JSON response: {parse_error}")
|
|
276
|
+
self.logger.error(f"Raw response: {response_body}")
|
|
277
|
+
# Don't mask parsing errors - let them bubble up
|
|
278
|
+
raise Exception(f"Failed to parse CollabLand API response: {parse_error}")
|
|
279
|
+
else:
|
|
280
|
+
# Handle error responses - raise exceptions for authentication errors instead of returning False
|
|
281
|
+
error_text = await response.text()
|
|
282
|
+
self.logger.error(f"CollabLand API error: {response.status} - {error_text}")
|
|
283
|
+
|
|
284
|
+
# Parse error response to get detailed error message
|
|
285
|
+
error_details = error_text
|
|
286
|
+
try:
|
|
287
|
+
import json
|
|
288
|
+
error_json = json.loads(error_text)
|
|
289
|
+
if isinstance(error_json, dict) and "error" in error_json:
|
|
290
|
+
error_info = error_json["error"]
|
|
291
|
+
if isinstance(error_info, dict):
|
|
292
|
+
error_details = error_info.get("message", error_text)
|
|
293
|
+
except (json.JSONDecodeError, ValueError, KeyError, TypeError):
|
|
294
|
+
# If we can't parse the error JSON, use the raw text
|
|
295
|
+
pass
|
|
296
|
+
|
|
297
|
+
# Raise specific exceptions for authentication errors to trigger Alchemy fallback
|
|
298
|
+
if response.status == 401 or response.status == 403:
|
|
299
|
+
self.logger.error("CollabLand API authentication error - check API key")
|
|
300
|
+
raise Exception(f"CollabLand authentication error (HTTP {response.status}): {error_details}")
|
|
301
|
+
elif response.status == 429:
|
|
302
|
+
self.logger.warning("CollabLand API rate limited - consider implementing backoff")
|
|
303
|
+
raise Exception(f"CollabLand rate limit error (HTTP {response.status}): {error_details}")
|
|
304
|
+
else:
|
|
305
|
+
# For other errors, also raise exceptions to trigger fallback
|
|
306
|
+
raise Exception(f"CollabLand API error (HTTP {response.status}): {error_details}")
|
|
307
|
+
|
|
308
|
+
except Exception as e:
|
|
309
|
+
# Only catch and log unexpected exceptions, but re-raise them so they can trigger fallback
|
|
310
|
+
self.logger.error(f"Error checking token for {address}: {e}")
|
|
311
|
+
# Log the full exception traceback for better debugging
|
|
312
|
+
import traceback
|
|
313
|
+
self.logger.error(f"Exception traceback: {traceback.format_exc()}")
|
|
314
|
+
|
|
315
|
+
# Check if this is an authentication error we should propagate
|
|
316
|
+
error_str = str(e).lower()
|
|
317
|
+
if any(keyword in error_str for keyword in ["authentication", "unauthorized", "invalid client", "forbidden", "api key"]):
|
|
318
|
+
# Re-raise authentication errors to trigger Alchemy fallback
|
|
319
|
+
raise e
|
|
320
|
+
elif "rate limit" in error_str or "429" in error_str:
|
|
321
|
+
# Re-raise rate limit errors to trigger Alchemy fallback
|
|
322
|
+
raise e
|
|
323
|
+
else:
|
|
324
|
+
# For other unexpected errors, also re-raise to trigger fallback
|
|
325
|
+
raise e
|
|
326
|
+
|
|
327
|
+
# ---------------------------------------------------------------------
|
|
328
|
+
# Database helpers
|
|
329
|
+
# ---------------------------------------------------------------------
|
|
330
|
+
|
|
331
|
+
async def _update_user_token_status(self, user_id: str, has_token: bool, token_id: Optional[str] = None) -> None:
|
|
332
|
+
"""Persist the `has_token` flag inside database_manager.
|
|
333
|
+
|
|
334
|
+
The concrete schema is application-specific. We assume a "users" table with
|
|
335
|
+
at least columns `(id, has_token)` and upsert semantics exposed through
|
|
336
|
+
`database_manager.set_user_flag`.
|
|
337
|
+
"""
|
|
338
|
+
db = self.database
|
|
339
|
+
if not db:
|
|
340
|
+
self.logger.warning("database_manager missing – cannot persist token status")
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
if hasattr(db, "set_user_flag"):
|
|
345
|
+
await db.set_user_flag(user_id, "has_token", has_token)
|
|
346
|
+
if token_id:
|
|
347
|
+
await db.set_user_flag(user_id, "token_id", token_id)
|
|
348
|
+
else:
|
|
349
|
+
# Fallback: generic upsert API
|
|
350
|
+
await db.upsert("users", {"id": user_id, "has_token": has_token})
|
|
351
|
+
if token_id:
|
|
352
|
+
await db.upsert("users", {"id": user_id, "token_id": token_id})
|
|
353
|
+
except Exception as exc:
|
|
354
|
+
self.logger.error(f"Failed to update DB for user {user_id}: {exc}")
|
|
355
|
+
|
|
356
|
+
# ---------------------------------------------------------------------
|
|
357
|
+
# Public actions
|
|
358
|
+
# ---------------------------------------------------------------------
|
|
359
|
+
|
|
360
|
+
@BaseTool.action(
|
|
361
|
+
"Check if the provided wallet address owns the required Collab.Land token(s)",
|
|
362
|
+
param_model=CheckTokenParams,
|
|
363
|
+
)
|
|
364
|
+
async def collabland_check_token(self, params: CheckTokenParams):
|
|
365
|
+
"""Return a human-readable message and persist the result in DB."""
|
|
366
|
+
await self.ensure_initialized()
|
|
367
|
+
|
|
368
|
+
if not self._enabled:
|
|
369
|
+
self.logger.error("CollabLand service is disabled (missing API key) - token verification failed")
|
|
370
|
+
return {
|
|
371
|
+
"status": "error",
|
|
372
|
+
"has_token": False,
|
|
373
|
+
"token_id": None,
|
|
374
|
+
"message": "Token verification service is currently unavailable. Please contact an administrator."
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
rules = params.rules or self._rules
|
|
378
|
+
if not rules:
|
|
379
|
+
self.logger.warning("No token-gating rules specified - verification will fail")
|
|
380
|
+
return {
|
|
381
|
+
"status": "error",
|
|
382
|
+
"has_token": False,
|
|
383
|
+
"token_id": None,
|
|
384
|
+
"message": "No token-gating rules available. Please contact an administrator."
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
# Normalise address
|
|
388
|
+
address = params.address.strip().lower()
|
|
389
|
+
|
|
390
|
+
try:
|
|
391
|
+
# Log the request details at INFO level for better visibility
|
|
392
|
+
self.logger.info(f"CollabLand token verification started for wallet: {address[:6]}...{address[-4:]}")
|
|
393
|
+
self.logger.info(f"Using rules: {rules}")
|
|
394
|
+
|
|
395
|
+
has_token, token_id = await self._check_roles(address, rules)
|
|
396
|
+
|
|
397
|
+
# Note: Database updates are handled by the calling service (den_onboarding_handler)
|
|
398
|
+
# We don't update the DB here to avoid table conflicts
|
|
399
|
+
|
|
400
|
+
# Mask address for user-facing messages
|
|
401
|
+
masked_address = f"{address[:6]}...{address[-4:]}"
|
|
402
|
+
|
|
403
|
+
if has_token:
|
|
404
|
+
message = f"✔️ Address `{masked_address}` holds the required token(s)."
|
|
405
|
+
if token_id:
|
|
406
|
+
message += f" Token ID: {token_id}"
|
|
407
|
+
self.logger.info(f"Token verification SUCCESS for {masked_address}: has_token=True, token_id={token_id or 'None'}")
|
|
408
|
+
else:
|
|
409
|
+
message = f"❌ Address `{masked_address}` does NOT meet the token-gating requirements."
|
|
410
|
+
self.logger.info(f"Token verification FAILED for {masked_address}: has_token=False")
|
|
411
|
+
|
|
412
|
+
# Create standardized response format
|
|
413
|
+
result = {
|
|
414
|
+
"status": "success",
|
|
415
|
+
"has_token": has_token,
|
|
416
|
+
"token_id": token_id,
|
|
417
|
+
"message": message
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
# Log full result at INFO level
|
|
421
|
+
self.logger.info(f"CollabLand verification complete: {result}")
|
|
422
|
+
|
|
423
|
+
return result
|
|
424
|
+
except Exception as e:
|
|
425
|
+
self.logger.error(f"Failed to check token for {address}: {e}")
|
|
426
|
+
# Get full exception details
|
|
427
|
+
import traceback
|
|
428
|
+
self.logger.error(f"Exception traceback: {traceback.format_exc()}")
|
|
429
|
+
return {
|
|
430
|
+
"status": "error",
|
|
431
|
+
"has_token": False,
|
|
432
|
+
"token_id": None,
|
|
433
|
+
"message": f"Error checking token: {str(e)}"
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
@BaseTool.action(
|
|
437
|
+
"Admin: batch refresh token status for all known users",
|
|
438
|
+
param_model=AdminRefreshParams,
|
|
439
|
+
)
|
|
440
|
+
async def collabland_admin_refresh(self, params: AdminRefreshParams):
|
|
441
|
+
"""Iterate over all users stored in DB and refresh their token status."""
|
|
442
|
+
await self.ensure_initialized()
|
|
443
|
+
|
|
444
|
+
if not params.confirm:
|
|
445
|
+
return (
|
|
446
|
+
"Confirmation missing – set `confirm=true` in parameters to execute the "
|
|
447
|
+
"batch refresh."
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
db = self.database
|
|
451
|
+
if not db or not hasattr(db, "get_all_users"):
|
|
452
|
+
raise ToolError("database_manager does not expose get_all_users() API")
|
|
453
|
+
|
|
454
|
+
users: List[Dict[str, Any]] = await db.get_all_users()
|
|
455
|
+
total, updated = len(users), 0
|
|
456
|
+
|
|
457
|
+
for user in users:
|
|
458
|
+
address = user.get("id") or user.get("wallet") or user.get("address")
|
|
459
|
+
if not address:
|
|
460
|
+
continue
|
|
461
|
+
|
|
462
|
+
try:
|
|
463
|
+
has_token, token_id = await self._check_roles(address, self._rules)
|
|
464
|
+
current_flag = bool(user.get("has_token"))
|
|
465
|
+
if has_token != current_flag:
|
|
466
|
+
await self._update_user_token_status(address, has_token)
|
|
467
|
+
updated += 1
|
|
468
|
+
except Exception as exc:
|
|
469
|
+
self.logger.warning(f"Refresh failed for {address}: {exc}")
|
|
470
|
+
|
|
471
|
+
return f"Batch refresh completed – {updated}/{total} records updated."
|
|
File without changes
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Shared Controller helpers (UP-11 god-file split).
|
|
2
|
+
|
|
3
|
+
Extracted from `tools/controller/service.py` so the focused Controller mixins
|
|
4
|
+
(execution / tool_management / introspection / action_registration) can import
|
|
5
|
+
these symbols WITHOUT importing `service.py` (which would be a circular import,
|
|
6
|
+
since service.py imports the mixins). `service.py` re-exports all four so the
|
|
7
|
+
established `from tools.controller.service import ToolInfo / make_denylist_hook /
|
|
8
|
+
build_load_skill_result` call sites (tests + callers) keep working unchanged.
|
|
9
|
+
|
|
10
|
+
Pure code-motion — bodies are verbatim.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Any, Callable, Dict
|
|
16
|
+
|
|
17
|
+
from tools.controller.types import ActionResult
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Define observe function to replace the import
|
|
21
|
+
def observe(*args, **_kwargs):
|
|
22
|
+
def decorator(func):
|
|
23
|
+
return func
|
|
24
|
+
if len(args) == 1 and callable(args[0]):
|
|
25
|
+
return decorator(args[0])
|
|
26
|
+
return decorator
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ToolInfo:
|
|
31
|
+
"""Information about a registered tool."""
|
|
32
|
+
instance: Any
|
|
33
|
+
actions: Dict[str, Callable]
|
|
34
|
+
name: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def make_denylist_hook(denied_action_names):
|
|
38
|
+
"""Build a pre_tool_call hook that denies the given action names.
|
|
39
|
+
|
|
40
|
+
Returns a hook ``(action_name, params, context) -> Optional[str]`` suitable for
|
|
41
|
+
``Controller.register_pre_tool_call_hook``. Useful for allow/deny lists.
|
|
42
|
+
"""
|
|
43
|
+
denied = set(denied_action_names or [])
|
|
44
|
+
|
|
45
|
+
def _hook(action_name, params, context):
|
|
46
|
+
if action_name in denied:
|
|
47
|
+
return f"'{action_name}' is on the tool denylist"
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
return _hook
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def list_skill_resources(skill_dir, *, max_files: int = 50):
|
|
54
|
+
"""List a skill's on-disk resource files (Task 17), relative to ``skill_dir``.
|
|
55
|
+
|
|
56
|
+
Excludes ``SKILL.md`` itself (that's the pinned body, not a "resource") and
|
|
57
|
+
any ``.git`` internals. Read-only listing — never inspects file content.
|
|
58
|
+
Fail-open: an unresolvable/missing dir returns ``[]``.
|
|
59
|
+
"""
|
|
60
|
+
from pathlib import Path
|
|
61
|
+
out = []
|
|
62
|
+
try:
|
|
63
|
+
base = Path(skill_dir).resolve()
|
|
64
|
+
except OSError:
|
|
65
|
+
return out
|
|
66
|
+
if not base.is_dir():
|
|
67
|
+
return out
|
|
68
|
+
for p in sorted(base.rglob("*")):
|
|
69
|
+
if len(out) >= max_files:
|
|
70
|
+
break
|
|
71
|
+
if not p.is_file() or p.name == "SKILL.md" or ".git" in p.parts:
|
|
72
|
+
continue
|
|
73
|
+
try:
|
|
74
|
+
out.append(str(p.resolve().relative_to(base)))
|
|
75
|
+
except (ValueError, OSError):
|
|
76
|
+
continue
|
|
77
|
+
return out
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def read_skill_resource_confined(skill_dir, rel_path: str, *, max_bytes: int = 200_000):
|
|
81
|
+
"""Read a single skill resource file, realpath-confined to ``skill_dir`` (Task 17).
|
|
82
|
+
|
|
83
|
+
Read-only — this NEVER executes a resource (e.g. a ``scripts/*.sh`` file),
|
|
84
|
+
it only returns its text content, UP-06-wrapped as untrusted data (skill
|
|
85
|
+
resources may originate from a third-party skill author). Rejects ``../``
|
|
86
|
+
escapes, absolute paths, symlink escapes (via ``.resolve()`` + parents
|
|
87
|
+
check), and oversize files. Returns ``(ok, content_or_error_message)``.
|
|
88
|
+
"""
|
|
89
|
+
from pathlib import Path
|
|
90
|
+
from agents.task.agent.core.untrusted_wrap import wrap_untrusted
|
|
91
|
+
try:
|
|
92
|
+
base = Path(skill_dir).resolve()
|
|
93
|
+
target = (base / rel_path).resolve()
|
|
94
|
+
except OSError as e:
|
|
95
|
+
return False, f"cannot resolve resource: {e}"
|
|
96
|
+
if target != base and base not in target.parents:
|
|
97
|
+
return False, "resource path escapes skill directory"
|
|
98
|
+
if not target.is_file():
|
|
99
|
+
return False, "no such resource"
|
|
100
|
+
try:
|
|
101
|
+
if target.stat().st_size > max_bytes:
|
|
102
|
+
return False, f"resource too large (>{max_bytes} bytes)"
|
|
103
|
+
content = target.read_text(encoding="utf-8", errors="replace")
|
|
104
|
+
except OSError as e:
|
|
105
|
+
return False, f"read failed: {e}"
|
|
106
|
+
return True, wrap_untrusted("skill_resource", content)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def build_load_skill_result(session_skills, skill_id, activated=None, skill_dir=None) -> ActionResult:
|
|
110
|
+
"""Resolve a load_skill(skill_id) call to an ActionResult (S-1).
|
|
111
|
+
|
|
112
|
+
Pure so it is unit-testable without a live Controller. ``session_skills`` maps
|
|
113
|
+
skill_id -> object with a ``.content`` attribute (the full SKILL.md body).
|
|
114
|
+
Returns an error result for an unknown id, else the body wrapped in a
|
|
115
|
+
cache-preserving <skill> block tagged with ``metadata.skill_loaded``.
|
|
116
|
+
|
|
117
|
+
``activated`` (Task 12 — activation dedup): an optional session-scoped
|
|
118
|
+
``set`` of skill_ids already delivered via this call. When ``skill_id`` is
|
|
119
|
+
already in ``activated``, the full body is NOT re-emitted — a short
|
|
120
|
+
"already active" ack is returned instead (``metadata.skill_already_active =
|
|
121
|
+
True``), so a model that calls ``load_skill`` twice for the same id in one
|
|
122
|
+
session doesn't burn tokens re-printing an unchanged body. On a fresh id the
|
|
123
|
+
body is returned as before and the id is recorded into ``activated``
|
|
124
|
+
(mutated in place, e.g. a Controller's ``self._activated_skills``) so the
|
|
125
|
+
NEXT call for that id short-circuits. ``activated=None`` (the default)
|
|
126
|
+
disables tracking entirely — existing callers that don't pass it keep the
|
|
127
|
+
old always-return-the-body behavior, byte-identical.
|
|
128
|
+
|
|
129
|
+
``skill_dir`` (Task 17 — resource read-path): an optional on-disk directory
|
|
130
|
+
for this skill (resolved by the caller, e.g. ``SkillManager.resolve_skill_dir``).
|
|
131
|
+
When provided, ``metadata['skill_resources']`` is set to
|
|
132
|
+
``list_skill_resources(skill_dir)`` (``[]`` if the dir doesn't resolve to any
|
|
133
|
+
files) so the model can see what it may subsequently read via
|
|
134
|
+
``read_skill_resource``. ``skill_dir=None`` (the default) leaves ``metadata``
|
|
135
|
+
untouched — byte-identical to pre-Task-17 callers that don't pass it.
|
|
136
|
+
"""
|
|
137
|
+
sid = (skill_id or "").strip().strip('"')
|
|
138
|
+
session_skills = session_skills or {}
|
|
139
|
+
skill = session_skills.get(sid)
|
|
140
|
+
if skill is None:
|
|
141
|
+
available = ", ".join(session_skills.keys()) or "(none)"
|
|
142
|
+
return ActionResult(
|
|
143
|
+
error=f"Unknown skill_id '{sid}'. Available in this session: {available}",
|
|
144
|
+
include_in_memory=True,
|
|
145
|
+
)
|
|
146
|
+
if activated is not None and sid in activated:
|
|
147
|
+
result = ActionResult(
|
|
148
|
+
extracted_content=f"Skill '{sid}' is already active this session — no need to reload.",
|
|
149
|
+
include_in_memory=True,
|
|
150
|
+
metadata={'skill_already_active': True},
|
|
151
|
+
)
|
|
152
|
+
if skill_dir is not None:
|
|
153
|
+
result.metadata['skill_resources'] = list_skill_resources(skill_dir)
|
|
154
|
+
return result
|
|
155
|
+
body = getattr(skill, 'content', '') or ''
|
|
156
|
+
if activated is not None:
|
|
157
|
+
activated.add(sid)
|
|
158
|
+
result = ActionResult(
|
|
159
|
+
extracted_content=f'<skill id="{sid}">\n{body}\n</skill>',
|
|
160
|
+
include_in_memory=True,
|
|
161
|
+
metadata={'skill_loaded': sid},
|
|
162
|
+
)
|
|
163
|
+
if skill_dir is not None:
|
|
164
|
+
result.metadata['skill_resources'] = list_skill_resources(skill_dir)
|
|
165
|
+
return result
|