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,303 @@
|
|
|
1
|
+
"""Output-validation mixin (roadmap P9; code-motion from llm_runner.py).
|
|
2
|
+
|
|
3
|
+
Post-LLM output validation + the thin next_action / _get_llm_parameters helpers,
|
|
4
|
+
split out of LLMRunnerMixin. Agent composes OutputValidationMixin; call sites
|
|
5
|
+
(service.py _validate_output, step loop) unchanged via MRO.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, List, Optional
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, ConfigDict
|
|
12
|
+
|
|
13
|
+
from agents.task.agent.views import AgentOutput, ActionResult
|
|
14
|
+
from agents.task.agent.prompts import AgentMessagePrompt
|
|
15
|
+
from agents.task.utils import time_execution_async
|
|
16
|
+
from modules.llm.messages import BaseMessage, HumanMessage, SystemMessage
|
|
17
|
+
|
|
18
|
+
# P0-6: hard bound on the judge call so a slow/hung judge can never stall the run
|
|
19
|
+
# loop past this (fail-open on timeout). Overridable for tests via monkeypatch.
|
|
20
|
+
VALIDATION_JUDGE_TIMEOUT_SEC: float = 60.0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _Verdict:
|
|
24
|
+
"""Minimal is_valid/reason carrier for the tolerant judge parser (P0-6).
|
|
25
|
+
|
|
26
|
+
The judge's structured path returns a Pydantic ``ValidationResult`` (defined
|
|
27
|
+
locally in ``_validate_output``); the manual path routes through this instead
|
|
28
|
+
so the tolerant parser can live at module scope and be unit-tested directly.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
__slots__ = ("is_valid", "reason")
|
|
32
|
+
|
|
33
|
+
def __init__(self, is_valid: bool, reason: str):
|
|
34
|
+
self.is_valid = is_valid
|
|
35
|
+
self.reason = reason
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _parse_validation_verdict(text: str) -> "_Verdict":
|
|
39
|
+
"""Tolerantly extract an is_valid/reason verdict from a judge reply.
|
|
40
|
+
|
|
41
|
+
Mirrors the ladder in ``agents/task/goals/completion_judge.py``: try the
|
|
42
|
+
shared JSON extractor first; on a miss (a judge that narrates prose instead
|
|
43
|
+
of JSON — the documented prod failure mode) fall back to a keyword scan for
|
|
44
|
+
an explicit ``is_valid: false``. Defaults to VALID (pass) when the reply
|
|
45
|
+
carries no clear negative verdict, keeping the judge fail-open: it only
|
|
46
|
+
fails a turn on a verdict it can actually read as ``false``.
|
|
47
|
+
"""
|
|
48
|
+
import re
|
|
49
|
+
from agents.task.utils_json import extract_json_from_model_output
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
data = extract_json_from_model_output(text) or {}
|
|
53
|
+
if isinstance(data, dict) and 'is_valid' in data:
|
|
54
|
+
return _Verdict(
|
|
55
|
+
is_valid=bool(data.get('is_valid', True)),
|
|
56
|
+
reason=str(data.get('reason') or 'No reason provided'),
|
|
57
|
+
)
|
|
58
|
+
except Exception:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
# Keyword fallback: an explicit false verdict anywhere in the prose fails;
|
|
62
|
+
# anything else passes (fail-open). Match `"is_valid": false`, `is_valid=false`,
|
|
63
|
+
# `is_valid false`, tolerant of quotes/whitespace/casing.
|
|
64
|
+
if re.search(r'is_valid["\']?\s*[:=]?\s*false', text, re.IGNORECASE):
|
|
65
|
+
return _Verdict(is_valid=False, reason=text.strip()[:300] or 'Judge returned an invalid verdict')
|
|
66
|
+
return _Verdict(is_valid=True, reason='No clear negative verdict; passing (fail-open)')
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class OutputValidationMixin:
|
|
70
|
+
"""LLM output validation + next_action wrapper for Agent."""
|
|
71
|
+
|
|
72
|
+
def _get_llm_parameters(self) -> dict:
|
|
73
|
+
"""Extract LLM parameters for logging purposes - delegates to MessageManager"""
|
|
74
|
+
return self.message_manager.get_llm_parameters()
|
|
75
|
+
|
|
76
|
+
async def _validate_output(self) -> bool:
|
|
77
|
+
"""Validate the output of the last action is what the user wanted"""
|
|
78
|
+
browser_context = await self.get_browser_context()
|
|
79
|
+
has_browser = bool(browser_context and browser_context.session)
|
|
80
|
+
|
|
81
|
+
# A2: task-type-neutral base prompt — this judge now runs for chat/coding/MCP
|
|
82
|
+
# tasks too (the browser-only early-return was removed in T16), so the framing
|
|
83
|
+
# must not assume a browser. Browser-specific guidance is appended ONLY when a
|
|
84
|
+
# live browser session is present.
|
|
85
|
+
system_msg = (
|
|
86
|
+
f'You are a validator of an AI agent. '
|
|
87
|
+
f'Validate if the output of the last action is what the user wanted and if the task is completed. '
|
|
88
|
+
f'If the task is unclearly defined, you can let it pass. But if something is missing or the output does not satisfy the request, dont let it pass. '
|
|
89
|
+
)
|
|
90
|
+
if has_browser:
|
|
91
|
+
system_msg += (
|
|
92
|
+
f'The agent interacts with a browser; try to understand the page and help with '
|
|
93
|
+
f'suggestions like scroll, do x, ... to get the solution right. '
|
|
94
|
+
)
|
|
95
|
+
system_msg += (
|
|
96
|
+
f'Task to validate: {self.task}. Return a JSON object with 2 keys: is_valid and reason. '
|
|
97
|
+
f'is_valid is a boolean that indicates if the output is correct. '
|
|
98
|
+
f'reason is a string that explains why it is valid or not.'
|
|
99
|
+
f' example: {{"is_valid": false, "reason": "The user wanted to search for "cat photos", but the agent searched for "dog photos" instead."}}'
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if has_browser:
|
|
103
|
+
state = await browser_context.get_state()
|
|
104
|
+
content = AgentMessagePrompt(
|
|
105
|
+
state=state,
|
|
106
|
+
result=self._last_result,
|
|
107
|
+
include_attributes=self.include_attributes,
|
|
108
|
+
max_error_length=self.max_error_length,
|
|
109
|
+
)
|
|
110
|
+
msg = [SystemMessage(content=system_msg), content.get_user_message(self.use_vision)]
|
|
111
|
+
else:
|
|
112
|
+
# CO-F1: no live browser session (chat/coding/MCP/non-browser tasks) — judge
|
|
113
|
+
# the final answer as text instead of skipping validation. Previously this
|
|
114
|
+
# branch unconditionally returned True ("can't validate"), which made
|
|
115
|
+
# validate_output a no-op for every non-browser task.
|
|
116
|
+
result_text = "\n".join(
|
|
117
|
+
(r.extracted_content or r.error or "")
|
|
118
|
+
for r in (self._last_result or [])
|
|
119
|
+
if (r.extracted_content or r.error)
|
|
120
|
+
) or "(no output produced)"
|
|
121
|
+
msg = [
|
|
122
|
+
SystemMessage(content=system_msg),
|
|
123
|
+
HumanMessage(content=f"Agent's final output:\n{result_text}"),
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
class ValidationResult(BaseModel):
|
|
127
|
+
"""
|
|
128
|
+
Validation results.
|
|
129
|
+
"""
|
|
130
|
+
is_valid: bool
|
|
131
|
+
reason: str
|
|
132
|
+
|
|
133
|
+
# FIXED: Add model_config to prevent additionalProperties schema error
|
|
134
|
+
model_config = ConfigDict(extra='forbid')
|
|
135
|
+
|
|
136
|
+
# CO-F1: lazily provision the cheap 'judge' aux model on first use, only when
|
|
137
|
+
# validate_output is actually on (mirrors BackgroundReviewMixin's
|
|
138
|
+
# provision-at-point-of-use pattern) — construction.py no longer provisions it
|
|
139
|
+
# unconditionally. Fail-open to the main model; cached on self._judge_llm so
|
|
140
|
+
# repeated validations in the same session don't re-resolve it.
|
|
141
|
+
if getattr(self, "validate_output", False) and getattr(self, "_judge_llm", None) is None:
|
|
142
|
+
try:
|
|
143
|
+
# P2-9: await the async provisioning so building the judge client yields
|
|
144
|
+
# the loop instead of blocking it (run_coroutine_sync) for up to 60s.
|
|
145
|
+
self._judge_llm = await self._provision_aux_llm_async("judge")
|
|
146
|
+
except Exception as e:
|
|
147
|
+
self.logger.debug(f"judge aux model provisioning skipped: {e}")
|
|
148
|
+
self._judge_llm = None
|
|
149
|
+
|
|
150
|
+
# UP-10 2.1: route validation through the optional cheap 'judge' aux model when
|
|
151
|
+
# provisioned (AUX_MODEL_JUDGE / AUX_AUTO); fail-open to the main model. Default
|
|
152
|
+
# (no judge configured) is byte-identical to the legacy self.llm path.
|
|
153
|
+
judge_llm = getattr(self, "_judge_llm", None) or self.llm
|
|
154
|
+
|
|
155
|
+
# Structured output if the adapter supports it; native adapters generally
|
|
156
|
+
# don't (browser-use legacy), so degrade to a plain call + JSON parse rather
|
|
157
|
+
# than raising AttributeError. (native structured-output fallback, 2026-06.)
|
|
158
|
+
validator = None
|
|
159
|
+
if hasattr(judge_llm, 'with_structured_output'):
|
|
160
|
+
try:
|
|
161
|
+
validator = judge_llm.with_structured_output(ValidationResult, include_raw=True)
|
|
162
|
+
except Exception as schema_error:
|
|
163
|
+
self.logger.debug(f"Structured output unavailable for ValidationResult, using manual parse: {schema_error}")
|
|
164
|
+
validator = None
|
|
165
|
+
|
|
166
|
+
# P0-6: the judge is fail-OPEN end-to-end. An unparseable / erroring /
|
|
167
|
+
# hanging judge reply must NOT raise out of the run loop and kill the turn
|
|
168
|
+
# after the agent already called done() (the previous behaviour: a prose
|
|
169
|
+
# judge reply let extract_json_from_model_output raise ValueError straight
|
|
170
|
+
# through the bare `await self._validate_output()` at run_loop.py:459).
|
|
171
|
+
# Fail-open covers judge FAILURES only — a well-formed {"is_valid": false}
|
|
172
|
+
# verdict still fails validation (that's the feature). Mirrors the tolerant
|
|
173
|
+
# ladder the goal judge already uses (agents/task/goals/completion_judge.py).
|
|
174
|
+
import asyncio
|
|
175
|
+
import time as _time
|
|
176
|
+
_t0 = _time.time()
|
|
177
|
+
_resp_for_meter = None
|
|
178
|
+
try:
|
|
179
|
+
if validator is not None:
|
|
180
|
+
response: dict[str, Any] = await asyncio.wait_for(
|
|
181
|
+
validator.ainvoke(msg), timeout=VALIDATION_JUDGE_TIMEOUT_SEC,
|
|
182
|
+
) # type: ignore
|
|
183
|
+
_resp_for_meter = response # extract_token_usage unwraps {'raw': ...}
|
|
184
|
+
raw_parsed = response.get('parsed') if isinstance(response, dict) else None
|
|
185
|
+
if raw_parsed is None:
|
|
186
|
+
# LOW-1: structured output can yield {'parsed': None, 'raw': ...}
|
|
187
|
+
# on a schema-mismatch — treat as a parse failure → fail-open.
|
|
188
|
+
self.logger.warning(
|
|
189
|
+
'⚠️ Judge structured output returned no parsed verdict; '
|
|
190
|
+
'passing (fail-open).'
|
|
191
|
+
)
|
|
192
|
+
return True
|
|
193
|
+
parsed = raw_parsed
|
|
194
|
+
else:
|
|
195
|
+
raw = await asyncio.wait_for(
|
|
196
|
+
judge_llm.ainvoke(msg), timeout=VALIDATION_JUDGE_TIMEOUT_SEC,
|
|
197
|
+
)
|
|
198
|
+
_resp_for_meter = raw
|
|
199
|
+
content = getattr(raw, 'content', raw)
|
|
200
|
+
parsed = _parse_validation_verdict(
|
|
201
|
+
content if isinstance(content, str) else str(content)
|
|
202
|
+
)
|
|
203
|
+
except asyncio.TimeoutError:
|
|
204
|
+
self.logger.warning(
|
|
205
|
+
f'⚠️ Judge validation timed out after {VALIDATION_JUDGE_TIMEOUT_SEC}s; '
|
|
206
|
+
'passing (fail-open).'
|
|
207
|
+
)
|
|
208
|
+
return True
|
|
209
|
+
except Exception as e: # noqa: BLE001 — any judge failure is fail-open
|
|
210
|
+
self.logger.warning(f'⚠️ Judge validation errored ({e!r}); passing (fail-open).')
|
|
211
|
+
return True
|
|
212
|
+
|
|
213
|
+
# A3: meter this aux LLM call through the single deduction path (fail-open).
|
|
214
|
+
# Only reached on a successful invoke — a failed/timed-out call is never metered.
|
|
215
|
+
from agents.task.agent.core.aux_metering import meter_aux_llm
|
|
216
|
+
await meter_aux_llm(
|
|
217
|
+
usage_tracker=getattr(self, "usage_tracker", None),
|
|
218
|
+
user_id=getattr(self, "user_id", None),
|
|
219
|
+
session_id=getattr(self, "session_id", ""),
|
|
220
|
+
agent_id=getattr(self, "agent_id", "") or "",
|
|
221
|
+
llm=judge_llm, response=_resp_for_meter, duration_seconds=_time.time() - _t0,
|
|
222
|
+
component="judge", purpose="output_validation",
|
|
223
|
+
)
|
|
224
|
+
is_valid = parsed.is_valid
|
|
225
|
+
if not is_valid:
|
|
226
|
+
self.logger.info(f'❌ Validator decision: {parsed.reason}')
|
|
227
|
+
msg = f'The output is not yet correct. {parsed.reason}.'
|
|
228
|
+
self._last_result = [ActionResult(extracted_content=msg, include_in_memory=True)]
|
|
229
|
+
else:
|
|
230
|
+
self.logger.info(f'✅ Validator decision: {parsed.reason}')
|
|
231
|
+
return is_valid
|
|
232
|
+
|
|
233
|
+
async def next_action(self, state: BrowserState, result: Optional[List[ActionResult]] = None) -> AgentOutput:
|
|
234
|
+
"""
|
|
235
|
+
Get the next action from the agent based on current state.
|
|
236
|
+
|
|
237
|
+
This is a simplified method that delegates to get_next_action for the actual LLM call
|
|
238
|
+
while providing a cleaner interface for external usage.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
state: Current browser state
|
|
242
|
+
result: Optional previous action results
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
AgentOutput with the next action to take
|
|
246
|
+
"""
|
|
247
|
+
import time
|
|
248
|
+
|
|
249
|
+
# CRITICAL FIX: Apply base64 stripping to state before any token operations
|
|
250
|
+
if hasattr(state, 'page_content') and state.page_content:
|
|
251
|
+
from agents.task.robust_parse_config import RobustParseConfig
|
|
252
|
+
state.page_content = RobustParseConfig.strip_base64_images(state.page_content)
|
|
253
|
+
|
|
254
|
+
start_time = time.time()
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
# Store the result for context
|
|
258
|
+
if result:
|
|
259
|
+
self._last_result = result
|
|
260
|
+
|
|
261
|
+
# Add state and result to message context
|
|
262
|
+
# FIX (Jan 2026): Conditionally include browser state
|
|
263
|
+
include_browser = self._has_active_browser_usage()
|
|
264
|
+
self.message_manager.add_state_message(
|
|
265
|
+
state, result, None, self.use_vision,
|
|
266
|
+
include_browser_state=include_browser
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Get messages for LLM call
|
|
270
|
+
input_messages = self.message_manager.get_messages()
|
|
271
|
+
|
|
272
|
+
# Call the main next action method - this will handle its own telemetry
|
|
273
|
+
# so we don't send duplicate telemetry from this wrapper method
|
|
274
|
+
agent_output = await self.get_next_action(input_messages)
|
|
275
|
+
|
|
276
|
+
# P2-14: the LLM responded (even a parse-failed safe-default means the
|
|
277
|
+
# request was delivered) — drop the one-shot ephemerals now.
|
|
278
|
+
try:
|
|
279
|
+
self.message_manager.commit_ephemeral_consumption()
|
|
280
|
+
except Exception:
|
|
281
|
+
pass
|
|
282
|
+
|
|
283
|
+
duration = time.time() - start_time
|
|
284
|
+
|
|
285
|
+
# Only log timing since get_next_action already sent telemetry
|
|
286
|
+
self.logger.debug(f"next_action wrapper completed in {duration:.2f}s")
|
|
287
|
+
|
|
288
|
+
return agent_output
|
|
289
|
+
|
|
290
|
+
except Exception as e:
|
|
291
|
+
duration = time.time() - start_time
|
|
292
|
+
# P2-14: the LLM call ultimately failed — re-queue the ephemerals
|
|
293
|
+
# (correspondent reply / RECALL) so the next step re-includes them
|
|
294
|
+
# instead of losing them forever.
|
|
295
|
+
try:
|
|
296
|
+
self.message_manager.restore_ephemeral_on_failure()
|
|
297
|
+
except Exception:
|
|
298
|
+
pass
|
|
299
|
+
# Log error but don't send duplicate telemetry
|
|
300
|
+
self.logger.error(f"next_action failed after {duration:.2f}s: {e}", exc_info=True)
|
|
301
|
+
raise
|
|
302
|
+
|
|
303
|
+
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""C9: Auto-load project context files (CLAUDE.md / AGENTS.md / .cursorrules).
|
|
2
|
+
|
|
3
|
+
CLI-only, default-OFF on server (gated by ``AutonomyConfig.project_context_autoload()``
|
|
4
|
+
AND ``local_mode_enabled()`` in construction.py).
|
|
5
|
+
|
|
6
|
+
``load_project_context(root, *, cap_tokens)`` walks from *root* up to the git root,
|
|
7
|
+
finds the first occurrence of each recognised filename, runs the injection-threat scan
|
|
8
|
+
(fail-OPEN on import error, fail-CLOSED on scan error), caps total content to
|
|
9
|
+
*cap_tokens* via ``estimate_tokens_rough``, and returns the concatenated result.
|
|
10
|
+
Returns ``None`` if nothing is found or any unrecoverable error occurs (fully
|
|
11
|
+
fail-open at the outer level).
|
|
12
|
+
|
|
13
|
+
Safety properties:
|
|
14
|
+
- Skips any file whose path is flagged by ``is_secret_path``.
|
|
15
|
+
- Rejects any document whose content is flagged by the ``is_suspicious`` threat
|
|
16
|
+
scanner (fail-OPEN if the scanner is unavailable; fail-CLOSED if it raises).
|
|
17
|
+
- Truncates concatenated content to *cap_tokens* with an appended notice.
|
|
18
|
+
- All I/O errors are swallowed; the whole function returns ``None`` on exception.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import logging
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Optional
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
# Recognised context filenames, in descending precedence.
|
|
29
|
+
# Only ONE name is loaded: the highest-precedence name that exists anywhere on the
|
|
30
|
+
# walk wins, and its most-local occurrence is used. Recognised names are NOT
|
|
31
|
+
# concatenated (a repo with both AGENTS.md and CLAUDE.md loads only AGENTS.md).
|
|
32
|
+
# polyrob.md — native control name (target POLYROB without touching other agents' files)
|
|
33
|
+
# POLYROB.md — uppercase variant
|
|
34
|
+
# AGENTS.md — vendor-neutral standard (Codex/OpenClaw interop)
|
|
35
|
+
# CLAUDE.md — Claude Code interop
|
|
36
|
+
# .cursorrules — legacy
|
|
37
|
+
_CONTEXT_FILENAMES: tuple[str, ...] = (
|
|
38
|
+
"polyrob.md",
|
|
39
|
+
"POLYROB.md",
|
|
40
|
+
"AGENTS.md",
|
|
41
|
+
"CLAUDE.md",
|
|
42
|
+
".cursorrules",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Header template injected before each file's content so the model knows the source.
|
|
46
|
+
_FILE_HEADER_TPL = "<!-- project-context: {filename} -->"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def should_load_project_context(
|
|
50
|
+
*, autoload: bool, local: bool, server_mode: bool
|
|
51
|
+
) -> bool:
|
|
52
|
+
"""Decide whether to load project context, given the resolved gates.
|
|
53
|
+
|
|
54
|
+
- Local CLI single-owner: load when ``autoload`` is on (trusted framing).
|
|
55
|
+
- Server: load ONLY when ``server_mode`` is explicitly opted in (untrusted
|
|
56
|
+
framing). ``autoload`` defaults OFF on the server, so the default is no-load
|
|
57
|
+
→ byte-identical to the pre-Phase-2 behaviour.
|
|
58
|
+
"""
|
|
59
|
+
if local:
|
|
60
|
+
return autoload
|
|
61
|
+
return server_mode
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def resolve_project_context_root(
|
|
65
|
+
*, local: bool, cwd: str, workspace_dir: Optional[str]
|
|
66
|
+
) -> Optional[str]:
|
|
67
|
+
"""Pick the directory to search for project-context files, by tier.
|
|
68
|
+
|
|
69
|
+
- Local CLI: the process CWD (the user's project root).
|
|
70
|
+
- Server: the tenant's session ``workspace_dir`` ONLY — **never** the process
|
|
71
|
+
CWD, which on a multi-tenant deployment is the install dir (e.g.
|
|
72
|
+
``/opt/polyrob``). Reading CWD there would leak the deployment's own files
|
|
73
|
+
(or another tenant's) into every session. Returns ``None`` when no server
|
|
74
|
+
workspace is resolvable, so the loader simply loads nothing.
|
|
75
|
+
"""
|
|
76
|
+
if local:
|
|
77
|
+
return cwd
|
|
78
|
+
return workspace_dir
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def build_project_context_message(
|
|
82
|
+
*,
|
|
83
|
+
local: bool,
|
|
84
|
+
autoload: bool,
|
|
85
|
+
server_mode: bool,
|
|
86
|
+
cwd: str,
|
|
87
|
+
workspace_dir: Optional[str],
|
|
88
|
+
cap_tokens: int = 20000,
|
|
89
|
+
) -> Optional[str]:
|
|
90
|
+
"""End-to-end: decide → resolve the tier root → load → frame.
|
|
91
|
+
|
|
92
|
+
Returns the foundation-message body (trusted/steering on local, untrusted-DATA
|
|
93
|
+
wrapped on the server opt-in), or ``None`` when nothing should/could be loaded.
|
|
94
|
+
Pure except for the filesystem read in :func:`load_project_context`; the caller
|
|
95
|
+
resolves ``workspace_dir`` (via the path manager) and passes it in, so this stays
|
|
96
|
+
unit-testable without a live session.
|
|
97
|
+
"""
|
|
98
|
+
if not should_load_project_context(
|
|
99
|
+
autoload=autoload, local=local, server_mode=server_mode
|
|
100
|
+
):
|
|
101
|
+
return None
|
|
102
|
+
root = resolve_project_context_root(local=local, cwd=cwd, workspace_dir=workspace_dir)
|
|
103
|
+
if root is None:
|
|
104
|
+
return None
|
|
105
|
+
ctx = load_project_context(root, cap_tokens=cap_tokens)
|
|
106
|
+
if ctx is None:
|
|
107
|
+
return None
|
|
108
|
+
return frame_project_context(ctx, trusted=local)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def frame_project_context(content: str, *, trusted: bool) -> str:
|
|
112
|
+
"""Frame loaded project context for injection as a foundation message.
|
|
113
|
+
|
|
114
|
+
A project file is owner-authored config on the local CLI (trusted → returned
|
|
115
|
+
unchanged, read as steering). On the server the same file may come from a repo
|
|
116
|
+
the operator merely opened, so it is untrusted input: wrap it in
|
|
117
|
+
``<untrusted_tool_result>`` DATA delimiters so the model treats it as a
|
|
118
|
+
DESCRIPTION of the project, not as instructions it must obey. The secret-skip
|
|
119
|
+
and ``is_suspicious`` scan in :func:`load_project_context` still run first.
|
|
120
|
+
"""
|
|
121
|
+
if trusted:
|
|
122
|
+
return content
|
|
123
|
+
from agents.task.agent.core.untrusted_wrap import wrap_untrusted
|
|
124
|
+
|
|
125
|
+
return wrap_untrusted("project-context", content)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _find_git_root(start: Path) -> Optional[Path]:
|
|
129
|
+
"""Walk from *start* upward to the first directory containing ``.git``.
|
|
130
|
+
|
|
131
|
+
Returns the directory itself, or ``None`` if no ``.git`` marker is found
|
|
132
|
+
before reaching the filesystem root.
|
|
133
|
+
"""
|
|
134
|
+
current = start.resolve()
|
|
135
|
+
for _ in range(50): # hard cap: prevents infinite loops on pathological FSes
|
|
136
|
+
if (current / ".git").exists():
|
|
137
|
+
return current
|
|
138
|
+
parent = current.parent
|
|
139
|
+
if parent == current:
|
|
140
|
+
# Reached the filesystem root without finding .git.
|
|
141
|
+
return None
|
|
142
|
+
current = parent
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def load_project_context(
|
|
147
|
+
root: str | Path,
|
|
148
|
+
*,
|
|
149
|
+
cap_tokens: int = 20000,
|
|
150
|
+
) -> Optional[str]:
|
|
151
|
+
"""Load and return project context from recognised context files.
|
|
152
|
+
|
|
153
|
+
Walks from *root* upward to the git root, collects the first occurrence of
|
|
154
|
+
each name in ``_CONTEXT_FILENAMES``, filters secret/suspicious files, and
|
|
155
|
+
returns their concatenated content capped to *cap_tokens*.
|
|
156
|
+
|
|
157
|
+
Returns ``None`` when nothing is found or the whole function fails.
|
|
158
|
+
"""
|
|
159
|
+
try:
|
|
160
|
+
return _load_project_context_impl(Path(root), cap_tokens=cap_tokens)
|
|
161
|
+
except Exception as e:
|
|
162
|
+
logger.debug("load_project_context failed (non-fatal): %s", e)
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _load_project_context_impl(root: Path, *, cap_tokens: int) -> Optional[str]:
|
|
167
|
+
"""Implementation (raises on error; caller wraps in try/except)."""
|
|
168
|
+
from agents.task.agent.core.secret_guard import is_secret_path, estimate_tokens_rough
|
|
169
|
+
|
|
170
|
+
# Resolve the threat-scanner once; None means scanner unavailable (fail-OPEN).
|
|
171
|
+
try:
|
|
172
|
+
from modules.memory.task.threat_scan import is_suspicious
|
|
173
|
+
except Exception:
|
|
174
|
+
is_suspicious = None # type: ignore[assignment]
|
|
175
|
+
|
|
176
|
+
root_resolved = root.resolve()
|
|
177
|
+
git_root = _find_git_root(root_resolved)
|
|
178
|
+
# If there is no .git root, fall back to the provided root so at least the
|
|
179
|
+
# immediate directory is searched.
|
|
180
|
+
search_root = git_root if git_root is not None else root_resolved
|
|
181
|
+
|
|
182
|
+
# Collect candidate directories: from root upward to (and including) git_root.
|
|
183
|
+
dirs: list[Path] = []
|
|
184
|
+
current = root_resolved
|
|
185
|
+
while True:
|
|
186
|
+
dirs.append(current)
|
|
187
|
+
if current == search_root:
|
|
188
|
+
break
|
|
189
|
+
parent = current.parent
|
|
190
|
+
if parent == current:
|
|
191
|
+
break
|
|
192
|
+
current = parent
|
|
193
|
+
|
|
194
|
+
# Name-first precedence: walk filenames in precedence order; for each, find its
|
|
195
|
+
# most-local occurrence across the dirs. The FIRST name that yields a usable
|
|
196
|
+
# file wins, and we stop — recognised names are not concatenated (L1 fix).
|
|
197
|
+
found: list[tuple[str, str]] = [] # at most one (filename, content)
|
|
198
|
+
|
|
199
|
+
for filename in _CONTEXT_FILENAMES:
|
|
200
|
+
if found:
|
|
201
|
+
break
|
|
202
|
+
for directory in dirs: # dirs is most-local → git-root order
|
|
203
|
+
candidate = directory / filename
|
|
204
|
+
if not candidate.is_file():
|
|
205
|
+
continue
|
|
206
|
+
|
|
207
|
+
# Secret-path guard.
|
|
208
|
+
if is_secret_path(candidate, root=search_root):
|
|
209
|
+
logger.debug("project_context: skipping secret path %s", candidate)
|
|
210
|
+
continue
|
|
211
|
+
|
|
212
|
+
# Read the file.
|
|
213
|
+
try:
|
|
214
|
+
raw = candidate.read_text(encoding="utf-8", errors="replace")
|
|
215
|
+
except OSError as e:
|
|
216
|
+
logger.debug("project_context: could not read %s: %s", candidate, e)
|
|
217
|
+
continue
|
|
218
|
+
|
|
219
|
+
if not raw.strip():
|
|
220
|
+
logger.debug("project_context: %s is empty, skipping", candidate)
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
# Threat-scan (fail-OPEN on import absence; fail-CLOSED on scan error).
|
|
224
|
+
if is_suspicious is not None:
|
|
225
|
+
try:
|
|
226
|
+
flagged = is_suspicious(raw)
|
|
227
|
+
except Exception as scan_err:
|
|
228
|
+
logger.warning(
|
|
229
|
+
"project_context: %s rejected (scan error, fail-closed): %s",
|
|
230
|
+
candidate, scan_err,
|
|
231
|
+
)
|
|
232
|
+
continue
|
|
233
|
+
if flagged:
|
|
234
|
+
logger.warning(
|
|
235
|
+
"project_context: %s rejected (suspicious content)", candidate
|
|
236
|
+
)
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
found.append((filename, raw))
|
|
240
|
+
break # most-local occurrence of the winning name — stop walking dirs
|
|
241
|
+
|
|
242
|
+
if not found:
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
# Concatenate with per-file headers.
|
|
246
|
+
parts: list[str] = []
|
|
247
|
+
for filename, content in found:
|
|
248
|
+
header = _FILE_HEADER_TPL.format(filename=filename)
|
|
249
|
+
parts.append(f"{header}\n{content}")
|
|
250
|
+
|
|
251
|
+
combined = "\n\n".join(parts)
|
|
252
|
+
|
|
253
|
+
# Cap to cap_tokens.
|
|
254
|
+
total_tokens = estimate_tokens_rough(combined)
|
|
255
|
+
if total_tokens > cap_tokens:
|
|
256
|
+
# Truncate the combined text to approximately cap_tokens.
|
|
257
|
+
# chars = tokens * 4 (estimate_tokens_rough uses len // 4).
|
|
258
|
+
max_chars = cap_tokens * 4
|
|
259
|
+
truncated = combined[:max_chars]
|
|
260
|
+
notice = f"\n\n<!-- project-context: truncated to {cap_tokens} tokens -->"
|
|
261
|
+
combined = truncated + notice
|
|
262
|
+
logger.debug(
|
|
263
|
+
"project_context: truncated from ~%d to ~%d tokens", total_tokens, cap_tokens
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
logger.debug(
|
|
267
|
+
"project_context: loaded %d file(s) [%s], ~%d tokens",
|
|
268
|
+
len(found),
|
|
269
|
+
", ".join(name for name, _ in found),
|
|
270
|
+
estimate_tokens_rough(combined),
|
|
271
|
+
)
|
|
272
|
+
return combined
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Agent resource-management mixin (roadmap P9 decomposition; code-motion from service.py).
|
|
2
|
+
|
|
3
|
+
Bounded-collection memory management (init + cleanup) and the browser-context
|
|
4
|
+
accessor. Moved verbatim off the ``Agent`` god-file; ``Agent`` composes
|
|
5
|
+
``ResourceMixin`` (call sites unchanged via MRO).
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from typing import Dict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ResourceMixin:
|
|
14
|
+
"""Memory-management (bounded collections) + browser-context accessor for Agent."""
|
|
15
|
+
|
|
16
|
+
def _initialize_memory_management(self) -> None:
|
|
17
|
+
"""Initialize memory management using bounded collections."""
|
|
18
|
+
from utils.bounded_collections import BoundedDict
|
|
19
|
+
|
|
20
|
+
# Use BoundedDict for automatic LRU eviction
|
|
21
|
+
self._telemetry_requests = BoundedDict(max_size=100)
|
|
22
|
+
self._file_references = BoundedDict(max_size=500)
|
|
23
|
+
|
|
24
|
+
# NOTE: User message queue removed - managed by HITLManager
|
|
25
|
+
self._max_user_guidance_tokens = int(os.getenv("MAX_USER_GUIDANCE_TOKENS", "1000"))
|
|
26
|
+
|
|
27
|
+
self.logger.debug("Initialized memory management with bounded collections")
|
|
28
|
+
|
|
29
|
+
def cleanup_memory(self, force: bool = False) -> Dict[str, int]:
|
|
30
|
+
"""Clean up memory using bounded collections.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
force: If True, perform aggressive cleanup regardless of thresholds
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Dictionary with cleanup statistics
|
|
37
|
+
"""
|
|
38
|
+
from datetime import timedelta
|
|
39
|
+
|
|
40
|
+
cleanup_stats = {}
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
# 1. Telemetry cleanup - BoundedDict handles eviction automatically
|
|
44
|
+
if hasattr(self, '_telemetry_requests') and force:
|
|
45
|
+
removed = self._telemetry_requests.cleanup_old(max_age=timedelta(minutes=5))
|
|
46
|
+
if removed > 0:
|
|
47
|
+
cleanup_stats['telemetry_entries_removed'] = removed
|
|
48
|
+
|
|
49
|
+
# 2. File references cleanup - BoundedDict handles eviction automatically
|
|
50
|
+
if hasattr(self, '_file_references') and force:
|
|
51
|
+
removed = self._file_references.cleanup_old(max_age=timedelta(hours=1))
|
|
52
|
+
if removed > 0:
|
|
53
|
+
cleanup_stats['files_cleaned'] = removed
|
|
54
|
+
|
|
55
|
+
# 3. Browser state history (keep existing logic)
|
|
56
|
+
if hasattr(self, '_last_browser_states') and len(self._last_browser_states) > 3:
|
|
57
|
+
while len(self._last_browser_states) > 3:
|
|
58
|
+
self._last_browser_states.popleft()
|
|
59
|
+
cleanup_stats['states_removed'] = cleanup_stats.get('states_removed', 0) + 1
|
|
60
|
+
|
|
61
|
+
# 4. Force garbage collection if requested
|
|
62
|
+
if force:
|
|
63
|
+
import gc
|
|
64
|
+
collected = gc.collect()
|
|
65
|
+
cleanup_stats['gc_collected'] = collected
|
|
66
|
+
|
|
67
|
+
# Log results
|
|
68
|
+
total_cleaned = sum(v for k, v in cleanup_stats.items() if k != 'gc_collected')
|
|
69
|
+
if total_cleaned > 10 or force:
|
|
70
|
+
self.logger.info(f"Memory cleanup removed {total_cleaned} items: {cleanup_stats}")
|
|
71
|
+
elif total_cleaned > 0:
|
|
72
|
+
self.logger.debug(f"Memory cleanup: {cleanup_stats}")
|
|
73
|
+
|
|
74
|
+
return cleanup_stats
|
|
75
|
+
|
|
76
|
+
except Exception as e:
|
|
77
|
+
self.logger.error(f"Error during memory cleanup: {e}", exc_info=True)
|
|
78
|
+
return cleanup_stats
|
|
79
|
+
|
|
80
|
+
async def get_browser_context(self):
|
|
81
|
+
"""Get browser context from orchestrator.
|
|
82
|
+
|
|
83
|
+
BrowserManager maintains context cache internally (contexts_in_use),
|
|
84
|
+
so calling this multiple times returns the same context for this agent_id.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Current browser context or None if not available
|
|
88
|
+
"""
|
|
89
|
+
try:
|
|
90
|
+
return await self.orchestrator.get_browser_context(self.agent_id)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
self.logger.debug(f"Failed to get browser context: {e}")
|
|
93
|
+
return None
|