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,781 @@
|
|
|
1
|
+
"""Step execution loop mixin (code-motion from service.py)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import base64
|
|
7
|
+
import http
|
|
8
|
+
import importlib
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import subprocess
|
|
14
|
+
import traceback
|
|
15
|
+
import time
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from io import BytesIO
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Union, Tuple
|
|
20
|
+
from collections import deque # ADDED: Import deque for bounded collections
|
|
21
|
+
from agents.task.agent.tool_call_tracker import ToolCallTracker # Robust tool call ID tracking
|
|
22
|
+
|
|
23
|
+
from dotenv import load_dotenv
|
|
24
|
+
|
|
25
|
+
# Import centralized constants
|
|
26
|
+
from agents.task.constants import (
|
|
27
|
+
IMG_TOKENS,
|
|
28
|
+
LoopDetectionConfig,
|
|
29
|
+
DEFAULT_USER_ID,
|
|
30
|
+
MemoryConfig,
|
|
31
|
+
MAX_MCP_PER_STEP
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Import POLYROB exceptions
|
|
35
|
+
from core.exceptions import (
|
|
36
|
+
AgentError,
|
|
37
|
+
ValidationError as ROBValidationError,
|
|
38
|
+
LLMResponseError,
|
|
39
|
+
ToolError
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Import billing exception for fail-fast handling
|
|
43
|
+
from core.exceptions import InsufficientCreditsError
|
|
44
|
+
|
|
45
|
+
# Import centralized utilities to avoid repeated inline imports
|
|
46
|
+
from agents.task.utils_json import normalize_action_schema
|
|
47
|
+
# Model limits now come from modules.llm.model_registry
|
|
48
|
+
|
|
49
|
+
# Native message types
|
|
50
|
+
from modules.llm.messages import (
|
|
51
|
+
AIMessage,
|
|
52
|
+
BaseMessage,
|
|
53
|
+
HumanMessage,
|
|
54
|
+
SystemMessage,
|
|
55
|
+
ToolMessage,
|
|
56
|
+
)
|
|
57
|
+
from modules.llm.adapters import BaseChatModel
|
|
58
|
+
from core.exceptions import (
|
|
59
|
+
RateLimitError,
|
|
60
|
+
LLMError,
|
|
61
|
+
LLMRateLimitError,
|
|
62
|
+
LLMAuthenticationError,
|
|
63
|
+
LLMConnectionError,
|
|
64
|
+
LLMContextLengthError,
|
|
65
|
+
LLMResponseError,
|
|
66
|
+
LLMPermanentError,
|
|
67
|
+
LLMProviderExhaustedError
|
|
68
|
+
)
|
|
69
|
+
# PIL Image imported locally in save_screenshot() method where needed
|
|
70
|
+
from pydantic import BaseModel, ConfigDict, ValidationError
|
|
71
|
+
|
|
72
|
+
from tools.browser.views import BrowserStateHistory, BrowserState
|
|
73
|
+
from agents.task.agent.message_manager.service import MessageManager
|
|
74
|
+
from agents.task.agent.prompts import SystemPrompt, AgentMessagePrompt
|
|
75
|
+
from agents.task.agent.views import (
|
|
76
|
+
ActionModel,
|
|
77
|
+
AgentBrain,
|
|
78
|
+
AgentError,
|
|
79
|
+
AgentHistory,
|
|
80
|
+
AgentHistoryList,
|
|
81
|
+
AgentOutput,
|
|
82
|
+
AgentStepInfo,
|
|
83
|
+
ActionResult,
|
|
84
|
+
)
|
|
85
|
+
from tools.browser.context import BrowserContext
|
|
86
|
+
from tools.dom.views import DOMElementNode, SelectorMap
|
|
87
|
+
from agents.task.telemetry.views import (
|
|
88
|
+
HumanApprovalRequestedEvent,
|
|
89
|
+
HumanApprovalDecisionEvent,
|
|
90
|
+
TodoStatusEvent,
|
|
91
|
+
AgentRunTelemetryEvent,
|
|
92
|
+
AgentEndTelemetryEvent,
|
|
93
|
+
ProviderFailureEvent,
|
|
94
|
+
ProviderFallbackSuccessEvent,
|
|
95
|
+
)
|
|
96
|
+
# ProductTelemetry no longer directly used - accessed via TelemetryManager
|
|
97
|
+
from agents.task.utils import time_execution_async, detect_llm_provider, extract_token_usage
|
|
98
|
+
# Safely import Google API exceptions
|
|
99
|
+
try:
|
|
100
|
+
from google.api_core.exceptions import ResourceExhausted
|
|
101
|
+
except ImportError:
|
|
102
|
+
# Create a dummy exception class if google-api-core is not installed
|
|
103
|
+
class ResourceExhausted(Exception):
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
# Import from our own logging config
|
|
107
|
+
from agents.task.logging_config import get_task_logger
|
|
108
|
+
|
|
109
|
+
# Import centralized path management
|
|
110
|
+
from agents.task.path import pm
|
|
111
|
+
|
|
112
|
+
load_dotenv()
|
|
113
|
+
|
|
114
|
+
# Generic logger for the module itself (not instances)
|
|
115
|
+
logger = get_task_logger('agent')
|
|
116
|
+
|
|
117
|
+
T = TypeVar('T', bound=BaseModel)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _detect_service_for_action(action_name: str) -> str:
|
|
121
|
+
"""Lazy delegate to the module-level helper in service.py.
|
|
122
|
+
|
|
123
|
+
Defined here so the moved ``_step_impl`` body (which references
|
|
124
|
+
``_detect_service_for_action`` as a global) resolves at runtime without a
|
|
125
|
+
circular import at module load time.
|
|
126
|
+
"""
|
|
127
|
+
from agents.task.agent.service import _detect_service_for_action as _impl
|
|
128
|
+
return _impl(action_name)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _is_fatal_step_error(error_str: str, billing_failover_enabled: bool) -> bool:
|
|
132
|
+
"""Classify a step-loop exception as fatal (halt immediately) vs recoverable.
|
|
133
|
+
|
|
134
|
+
`error_str` must be lowercased. When `billing_failover_enabled` is True, billing/
|
|
135
|
+
quota errors are NOT fatal here â they flow to `_handle_step_error` which attempts a
|
|
136
|
+
provider swap before halting (HIGH-1). With the flag off, classification is identical
|
|
137
|
+
to the historical inline check (all billing/quota terms fatal).
|
|
138
|
+
"""
|
|
139
|
+
is_billing = 'insufficient_quota' in error_str or 'billing' in error_str
|
|
140
|
+
# CO-F5: bare '429'/'rate limit' are NOT fatal â they're retryable. Routing them
|
|
141
|
+
# here halted the session before `_handle_step_error` could handle them. In practice
|
|
142
|
+
# a plain-Exception '429'/'rate limit' string is caught by the generic `is_llm_error`
|
|
143
|
+
# block (error_recovery.py ~:203-296: matches 'rate limit'/'429', then circuit
|
|
144
|
+
# breaker â provider fallback â backoff), which now owns all retryable rate-limit
|
|
145
|
+
# handling.
|
|
146
|
+
return (
|
|
147
|
+
('quota' in error_str and 'exceeded' in error_str) or
|
|
148
|
+
'authentication' in error_str or
|
|
149
|
+
'api key' in error_str or
|
|
150
|
+
(is_billing and not billing_failover_enabled)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class StepMixin:
|
|
155
|
+
"""Mixin extracted verbatim from agents/task/agent/service.py (pure code-motion)."""
|
|
156
|
+
|
|
157
|
+
@time_execution_async('--step')
|
|
158
|
+
async def step(self, step_info: Optional[AgentStepInfo] = None) -> None:
|
|
159
|
+
"""Execute one step of the task with runtime safety guards"""
|
|
160
|
+
# ALWAYS use timeout protection - default to 600 seconds if not configured
|
|
161
|
+
timeout_seconds = self.step_timeout_seconds or 600
|
|
162
|
+
try:
|
|
163
|
+
# Track step start time for stall detection
|
|
164
|
+
self.state.last_step_start_time = time.time()
|
|
165
|
+
|
|
166
|
+
# Check AgentState loop detection BEFORE step
|
|
167
|
+
if self.state.is_stuck_in_loop():
|
|
168
|
+
self.logger.warning("đ¨ AgentState detected stuck loop")
|
|
169
|
+
self._trigger_loop_intervention("AgentState loop detection triggered (multi-signal)")
|
|
170
|
+
elif self.state.is_showing_loop_symptoms():
|
|
171
|
+
# Early warning - log but don't intervene
|
|
172
|
+
self.logger.info(
|
|
173
|
+
f"â ī¸ Loop symptoms detected: similar_actions={self.state.consecutive_similar_actions}, "
|
|
174
|
+
f"steps_since_finding={self.state.n_steps - self.state.last_finding_step}"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# H-MEM loop signal - log only, no intervention
|
|
178
|
+
if hasattr(self, 'task_context_manager') and self.task_context_manager:
|
|
179
|
+
hmem_signal = self.task_context_manager.check_loop_signal(self.session_id)
|
|
180
|
+
if hmem_signal:
|
|
181
|
+
self.logger.info(f"đ H-MEM: {hmem_signal}")
|
|
182
|
+
self.task_context_manager.reset_loop_signal(self.session_id)
|
|
183
|
+
|
|
184
|
+
async with asyncio.timeout(timeout_seconds):
|
|
185
|
+
await self._step_impl(step_info)
|
|
186
|
+
|
|
187
|
+
# Reset stall detection on successful step
|
|
188
|
+
self.state.last_activity_time = time.time()
|
|
189
|
+
|
|
190
|
+
except asyncio.TimeoutError:
|
|
191
|
+
self.logger.error(f"Step timed out after {timeout_seconds} seconds", exc_info=True)
|
|
192
|
+
self.state.consecutive_failures = self.max_failures
|
|
193
|
+
|
|
194
|
+
# Try to save progress before failing
|
|
195
|
+
try:
|
|
196
|
+
await self._save_progress_on_timeout()
|
|
197
|
+
except Exception as e:
|
|
198
|
+
self.logger.warning(f"Failed to save progress on timeout: {e}")
|
|
199
|
+
|
|
200
|
+
raise
|
|
201
|
+
|
|
202
|
+
async def _prepare_step(self, step_info: Optional[AgentStepInfo] = None):
|
|
203
|
+
"""Phase 1: pre-LLM setup â refresh context, build the state message, return (state, input_messages)."""
|
|
204
|
+
# Check cancellation before starting step execution
|
|
205
|
+
if self._cancelled:
|
|
206
|
+
self.logger.warning(f"â Step execution cancelled before start (step {self.state.n_steps})")
|
|
207
|
+
raise asyncio.CancelledError("Agent cancelled")
|
|
208
|
+
|
|
209
|
+
# Update step counter if step_info is provided
|
|
210
|
+
if step_info is not None and hasattr(step_info, 'step_number'):
|
|
211
|
+
# Use the provided step number + 1 (since step_number is 0-indexed)
|
|
212
|
+
self.state.n_steps = step_info.step_number + 1
|
|
213
|
+
|
|
214
|
+
# Use step number only in log to reduce verbosity
|
|
215
|
+
self.logger.info(f'đ Step {self.state.n_steps}')
|
|
216
|
+
|
|
217
|
+
# FIX: Invalidate H-MEM cache at start of each step to ensure fresh context
|
|
218
|
+
# This prevents duplicate hierarchical searches within a step
|
|
219
|
+
self.message_manager.invalidate_hmem_cache()
|
|
220
|
+
|
|
221
|
+
# FIX (Context Optimization Phase 2): Log context usage breakdown
|
|
222
|
+
# Log every 10 steps or first 3 steps for early visibility
|
|
223
|
+
if self.state.n_steps % 10 == 0 or self.state.n_steps <= 3:
|
|
224
|
+
self._log_context_breakdown()
|
|
225
|
+
|
|
226
|
+
# Periodic memory monitoring (less aggressive)
|
|
227
|
+
if self.state.n_steps % 10 == 0: # Check less frequently
|
|
228
|
+
try:
|
|
229
|
+
import psutil
|
|
230
|
+
import gc
|
|
231
|
+
# NOTE: `os` is module-level (top of file); do NOT re-import it locally â
|
|
232
|
+
# a function-local `import os` would shadow it for this whole method and
|
|
233
|
+
# turn any earlier os.* use into an UnboundLocalError.
|
|
234
|
+
|
|
235
|
+
process = psutil.Process(os.getpid())
|
|
236
|
+
memory_mb = process.memory_info().rss / 1024 / 1024
|
|
237
|
+
|
|
238
|
+
# NOTE: Removed truncation of _last_result - it was destroying important context
|
|
239
|
+
# Previous results are needed for task continuity
|
|
240
|
+
|
|
241
|
+
# Check memory usage
|
|
242
|
+
memory_bytes = self.message_manager.get_memory_usage_estimate()
|
|
243
|
+
memory_mb = memory_bytes / (1024 * 1024)
|
|
244
|
+
if memory_mb > 50: # If message history is using > 50MB
|
|
245
|
+
self.logger.warning(f"Message history using {memory_mb:.1f}MB - consider checkpointing")
|
|
246
|
+
|
|
247
|
+
# Periodic memory cleanup
|
|
248
|
+
self.cleanup_memory(force=False)
|
|
249
|
+
|
|
250
|
+
# Log if high memory usage
|
|
251
|
+
if memory_mb > 1000: # 1GB
|
|
252
|
+
self.logger.info(f"Memory usage: {memory_mb:.0f}MB at step {self.state.n_steps}")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
except Exception as e:
|
|
256
|
+
self.logger.debug(f"Memory cleanup error: {e}")
|
|
257
|
+
|
|
258
|
+
# Context compaction check - EVERY STEP with tiered thresholds
|
|
259
|
+
# FIX (Jan 2026): Changed from every 10 steps to every step
|
|
260
|
+
# MCP responses can add 60K+ tokens in a single step, causing overflow before check
|
|
261
|
+
try:
|
|
262
|
+
usage_pct = self.message_manager.get_context_usage_percent()
|
|
263
|
+
|
|
264
|
+
if usage_pct >= 95:
|
|
265
|
+
# CRITICAL: Emergency prune immediately - about to overflow.
|
|
266
|
+
# Non-LLM, always runs (the overflow safety net) - no cooldown.
|
|
267
|
+
self.logger.warning(f"đ¨ Critical context: {usage_pct:.1f}% - emergency prune")
|
|
268
|
+
self.message_manager.emergency_context_prune()
|
|
269
|
+
elif usage_pct >= 85:
|
|
270
|
+
# HIGH: LLM compaction to intelligently summarize.
|
|
271
|
+
# Flow-efficiency D3-a: llm_compact_history is an EXTRA LLM call. Apply a
|
|
272
|
+
# step cooldown so it does not re-fire every step while usage lingers in the
|
|
273
|
+
# 85-95% band (a single large MCP result can re-cross 85% each step). If it
|
|
274
|
+
# climbs to >=95%, the emergency prune above handles overflow without an LLM call.
|
|
275
|
+
from agents.task.constants import COMPACTION_COOLDOWN_STEPS
|
|
276
|
+
last_compaction = getattr(self, '_last_llm_compaction_step', None)
|
|
277
|
+
steps_since = (
|
|
278
|
+
self.state.n_steps - last_compaction
|
|
279
|
+
if last_compaction is not None else COMPACTION_COOLDOWN_STEPS
|
|
280
|
+
)
|
|
281
|
+
if steps_since >= COMPACTION_COOLDOWN_STEPS:
|
|
282
|
+
self.logger.info(f"đĻ High context: {usage_pct:.1f}% - LLM compaction")
|
|
283
|
+
# P2-15: only start the cooldown when compaction ACTUALLY ran. A
|
|
284
|
+
# transient abort (rate-limit/connection blip), a <15-message history,
|
|
285
|
+
# or an anti-thrash no-op returns False and is designed to retry next
|
|
286
|
+
# step â stamping the cooldown regardless delayed that retry by
|
|
287
|
+
# COMPACTION_COOLDOWN_STEPS while usage sat in the 85-95% band.
|
|
288
|
+
if await self.message_manager.llm_compact_history():
|
|
289
|
+
self._last_llm_compaction_step = self.state.n_steps
|
|
290
|
+
else:
|
|
291
|
+
self.logger.info(
|
|
292
|
+
f"đĻ High context: {usage_pct:.1f}% - compacted {steps_since} step(s) ago, "
|
|
293
|
+
f"skipping LLM compaction (cooldown {COMPACTION_COOLDOWN_STEPS})"
|
|
294
|
+
)
|
|
295
|
+
elif usage_pct >= 70:
|
|
296
|
+
# WARNING: Log for visibility every 5 steps at this level
|
|
297
|
+
if self.state.n_steps % 5 == 0:
|
|
298
|
+
self.logger.info(f"â ī¸ Context at {usage_pct:.1f}% - monitoring")
|
|
299
|
+
except Exception as e:
|
|
300
|
+
self.logger.debug(f"Context compaction error: {e}")
|
|
301
|
+
|
|
302
|
+
state = None
|
|
303
|
+
browser_context = None # bound before the try so the return is always safe
|
|
304
|
+
|
|
305
|
+
try:
|
|
306
|
+
# Get fresh browser context from orchestrator
|
|
307
|
+
browser_context = await self.get_browser_context()
|
|
308
|
+
|
|
309
|
+
# Only get browser state if we have a browser context
|
|
310
|
+
if browser_context:
|
|
311
|
+
try:
|
|
312
|
+
# FIX (Dec 2025): Only capture screenshots when vision is enabled
|
|
313
|
+
# This saves ~100-200ms per step for non-vision tasks
|
|
314
|
+
# Pass use_vision flag to control screenshot capture
|
|
315
|
+
state = await browser_context.get_state(capture_screenshot=self.use_vision)
|
|
316
|
+
|
|
317
|
+
# Apply page content truncation early for token safety
|
|
318
|
+
if hasattr(state, 'page_content') and state.page_content:
|
|
319
|
+
from agents.task.robust_parse_config import RobustParseConfig
|
|
320
|
+
state.page_content = RobustParseConfig.truncate_page_content(state.page_content)
|
|
321
|
+
except Exception as e:
|
|
322
|
+
self.logger.warning(f"Error getting browser state: {str(e)}")
|
|
323
|
+
# Don't fail - just continue without browser state
|
|
324
|
+
state = None
|
|
325
|
+
else:
|
|
326
|
+
# No browser context - use a minimal state
|
|
327
|
+
self.logger.debug("No browser context available - running without browser state")
|
|
328
|
+
# Create a minimal state object for non-browser tasks
|
|
329
|
+
from agents.task.agent.views import BrowserState
|
|
330
|
+
from tools.dom.views import DOMElementNode, SelectorMap
|
|
331
|
+
# BrowserState requires element_tree and selector_map from DOMState
|
|
332
|
+
# Create a minimal DOMElementNode instance
|
|
333
|
+
minimal_element = DOMElementNode(
|
|
334
|
+
tag_name='html',
|
|
335
|
+
xpath='/',
|
|
336
|
+
attributes={},
|
|
337
|
+
children=[],
|
|
338
|
+
is_visible=True,
|
|
339
|
+
parent=None
|
|
340
|
+
)
|
|
341
|
+
state = BrowserState(
|
|
342
|
+
url="",
|
|
343
|
+
title="No Browser",
|
|
344
|
+
tabs=[],
|
|
345
|
+
element_tree=minimal_element,
|
|
346
|
+
selector_map={}
|
|
347
|
+
)
|
|
348
|
+
except Exception as e:
|
|
349
|
+
self.logger.error(f"Unexpected error in state setup: {str(e)}", exc_info=True)
|
|
350
|
+
# Create minimal state as fallback
|
|
351
|
+
from agents.task.agent.views import BrowserState
|
|
352
|
+
from tools.dom.views import DOMElementNode, SelectorMap
|
|
353
|
+
# Create a minimal DOMElementNode for no-browser state
|
|
354
|
+
empty_element = DOMElementNode(
|
|
355
|
+
tag_name="html",
|
|
356
|
+
xpath="/html",
|
|
357
|
+
attributes={},
|
|
358
|
+
children=[]
|
|
359
|
+
)
|
|
360
|
+
state = BrowserState(
|
|
361
|
+
url="",
|
|
362
|
+
title="No Browser",
|
|
363
|
+
tabs=[],
|
|
364
|
+
element_tree=empty_element,
|
|
365
|
+
selector_map=SelectorMap()
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
self._check_if_stopped()
|
|
369
|
+
|
|
370
|
+
# FIXED: Force recalibration after large actions
|
|
371
|
+
from agents.task.robust_parse_config import RobustParseConfig
|
|
372
|
+
should_recalibrate = False
|
|
373
|
+
|
|
374
|
+
# Check if we need to recalibrate after large actions
|
|
375
|
+
if (RobustParseConfig.FORCE_RECALIBRATE_AFTER_LARGE_ACTIONS and
|
|
376
|
+
self._last_result and any(
|
|
377
|
+
result.extracted_content and len(result.extracted_content) > RobustParseConfig.LARGE_ACTION_THRESHOLD
|
|
378
|
+
for result in self._last_result
|
|
379
|
+
)):
|
|
380
|
+
should_recalibrate = True
|
|
381
|
+
self.logger.debug("Forcing recalibration after large action result")
|
|
382
|
+
|
|
383
|
+
# Recalibrate token counts on first step, every 5 steps, or after large actions
|
|
384
|
+
if self.state.n_steps == 1 or self.state.n_steps % 5 == 0 or should_recalibrate:
|
|
385
|
+
self.message_manager.recalibrate_token_counts()
|
|
386
|
+
|
|
387
|
+
# Pass previous brain state for memory continuity
|
|
388
|
+
previous_brain = None
|
|
389
|
+
if hasattr(self, '_last_model_output') and self._last_model_output and hasattr(self._last_model_output, 'current_state'):
|
|
390
|
+
previous_brain = self._last_model_output.current_state
|
|
391
|
+
|
|
392
|
+
# FIX (Jan 2026): Conditionally include browser state to prevent context bleeding
|
|
393
|
+
# The AgentMessagePrompt will also check _has_meaningful_browser_state()
|
|
394
|
+
# so even if we pass True here, empty states won't bleed through
|
|
395
|
+
include_browser = self._has_active_browser_usage()
|
|
396
|
+
|
|
397
|
+
# B-T2: prefetch & inject recalled memory (first step, then every
|
|
398
|
+
# MEMORY_PREFETCH_CADENCE steps when set). Fail-open; inert unless an
|
|
399
|
+
# external memory provider is registered.
|
|
400
|
+
await self._maybe_prefetch_memory()
|
|
401
|
+
|
|
402
|
+
# Task 5: session-start "recent activity" digest â first step only, chat/
|
|
403
|
+
# owner sessions only (never goal/cron/sub-agent). Fail-open; inert unless
|
|
404
|
+
# AutonomyConfig.episodic_digest_inject() is on.
|
|
405
|
+
await self._maybe_inject_episodic_digest()
|
|
406
|
+
|
|
407
|
+
# Task 6: idle-reset continuity bridge â first step only, chat/owner sessions
|
|
408
|
+
# only (never goal/cron/sub-agent). Fail-open; inert unless
|
|
409
|
+
# AutonomyConfig.continuity_bridge_enabled() is on and a prior chat episode
|
|
410
|
+
# with a summary exists for this session's thread_key.
|
|
411
|
+
await self._maybe_inject_continuity_bridge()
|
|
412
|
+
|
|
413
|
+
# §7.5: autonomous continuity bridge â first step only, AUTONOMOUS sessions
|
|
414
|
+
# only (goal/cron; never chat/sub-agent). Carries recent activity into the
|
|
415
|
+
# tick so it stops re-deriving "nothing new". Fail-open; inert unless
|
|
416
|
+
# AutonomyConfig.autonomous_continuity_bridge() is on.
|
|
417
|
+
await self._maybe_inject_autonomous_continuity()
|
|
418
|
+
|
|
419
|
+
# CO-F7: mark the once-per-SESSION bootstrap as done immediately after
|
|
420
|
+
# both injectors above have had their one shot on the session's first
|
|
421
|
+
# executed step. This is deliberately set here (not at the end of
|
|
422
|
+
# run()) so that a turn-1 run() that breaks BEFORE step() ever runs
|
|
423
|
+
# (cancellation, resumed-from-done/stopped, too-many-failures) leaves
|
|
424
|
+
# the flag False â a later real turn still gets to inject once. A
|
|
425
|
+
# _continue_session=True turn resets n_steps too, so its own first step
|
|
426
|
+
# also lands here; the injectors' own `_session_bootstrap_done` guard
|
|
427
|
+
# (see memory_prefetch.py) makes this idempotent â they've already
|
|
428
|
+
# fired-or-declined by the time this line runs, so re-setting an
|
|
429
|
+
# already-True flag is a no-op.
|
|
430
|
+
if self.state.n_steps == 1:
|
|
431
|
+
self._session_bootstrap_done = True
|
|
432
|
+
|
|
433
|
+
self.message_manager.add_state_message(
|
|
434
|
+
state,
|
|
435
|
+
self._last_result,
|
|
436
|
+
step_info,
|
|
437
|
+
self.use_vision,
|
|
438
|
+
previous_brain,
|
|
439
|
+
include_browser_state=include_browser
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# Planning artifacts (plan.json, todo.md) are maintained directly by the agent
|
|
443
|
+
|
|
444
|
+
# User messages are drained in the run loop (after each step and after completion)
|
|
445
|
+
# This prevents unnecessary LLM calls just to check for new messages
|
|
446
|
+
# See run() method for message draining logic
|
|
447
|
+
|
|
448
|
+
input_messages = self.message_manager.get_messages()
|
|
449
|
+
|
|
450
|
+
# DIAGNOSTIC: Log control flags before check to help debug unexpected stops
|
|
451
|
+
if self.state.stopped:
|
|
452
|
+
self.logger.info(f'â ī¸ Control flags before get_next_action: stopped={self.state.stopped}')
|
|
453
|
+
|
|
454
|
+
self._check_if_stopped()
|
|
455
|
+
|
|
456
|
+
return state, input_messages, browser_context
|
|
457
|
+
|
|
458
|
+
async def _record_step(self, state, model_output, input_messages) -> None:
|
|
459
|
+
"""Recording tail of a step: persist last model output, run loop/repetition
|
|
460
|
+
detection, fire the new-step callback, save the conversation, and refresh
|
|
461
|
+
the state message. Extracted verbatim from _step_impl (behavior-preserving).
|
|
462
|
+
"""
|
|
463
|
+
# Store model output for next step
|
|
464
|
+
self._last_model_output = model_output
|
|
465
|
+
|
|
466
|
+
# FIX #4: Track action in AgentState for loop detection
|
|
467
|
+
if model_output and hasattr(model_output, 'current_state') and model_output.current_state:
|
|
468
|
+
action_summary_for_tracking = model_output.current_state.memory[:200] if model_output.current_state.memory else "No memory"
|
|
469
|
+
self.state.track_action(action_summary_for_tracking)
|
|
470
|
+
|
|
471
|
+
# Action repetition detection
|
|
472
|
+
if model_output and hasattr(model_output, 'action') and model_output.action:
|
|
473
|
+
# Create a simplified representation of the action for comparison
|
|
474
|
+
import hashlib
|
|
475
|
+
# --- UPGRADE: use order-independent semantic hash ----------------
|
|
476
|
+
action_dump = []
|
|
477
|
+
for a in model_output.action:
|
|
478
|
+
# Dump each action with sorted keys so param order does not matter
|
|
479
|
+
action_dump.append(a.model_dump(mode="json", exclude_unset=True, by_alias=True))
|
|
480
|
+
action_hash = hashlib.md5(json.dumps(action_dump, sort_keys=True).encode()).hexdigest()
|
|
481
|
+
# ---------------------------------------------------------------
|
|
482
|
+
|
|
483
|
+
# Check for repeated actions with improved detection
|
|
484
|
+
if self._previous_actions and action_hash == self._previous_actions[-1]:
|
|
485
|
+
self._action_repetition_counter += 1
|
|
486
|
+
# Log warnings after 3+ repetitions (more aggressive)
|
|
487
|
+
if self._action_repetition_counter >= 3:
|
|
488
|
+
self.logger.warning(f"Same action repeated {self._action_repetition_counter} times")
|
|
489
|
+
|
|
490
|
+
# FIX #1: Respect the configured threshold instead of hardcoding 8
|
|
491
|
+
# The config value is typically 2-3 for faster loop detection
|
|
492
|
+
max_reps = self._max_allowed_repetitions # Removed the max(..., 8) override
|
|
493
|
+
if self._action_repetition_counter >= max_reps:
|
|
494
|
+
self._trigger_loop_intervention(f"Action repeated {self._action_repetition_counter} times")
|
|
495
|
+
else:
|
|
496
|
+
# Different action, reset counter
|
|
497
|
+
self._action_repetition_counter = 0
|
|
498
|
+
|
|
499
|
+
# Update action history (deque automatically maintains max size)
|
|
500
|
+
self._previous_actions.append(action_hash)
|
|
501
|
+
|
|
502
|
+
# FIX #2: Enhanced pattern detection with IMMEDIATE intervention
|
|
503
|
+
if len(self._previous_actions) >= 4:
|
|
504
|
+
# Convert deque to list for slice operations
|
|
505
|
+
prev_actions_list = list(self._previous_actions)
|
|
506
|
+
|
|
507
|
+
# Check for A-B-A-B pattern - IMMEDIATE intervention
|
|
508
|
+
if (prev_actions_list[-1] == prev_actions_list[-3] and
|
|
509
|
+
prev_actions_list[-2] == prev_actions_list[-4]):
|
|
510
|
+
self.logger.warning("đ Detected alternating action pattern (A-B-A-B) - INTERVENING NOW")
|
|
511
|
+
self._trigger_loop_intervention("Alternating A-B-A-B pattern detected")
|
|
512
|
+
|
|
513
|
+
# Check for A-B-C-A-B-C pattern - IMMEDIATE intervention
|
|
514
|
+
elif (len(self._previous_actions) >= 6 and
|
|
515
|
+
prev_actions_list[-3:] == prev_actions_list[-6:-3]):
|
|
516
|
+
self.logger.warning("đ Detected repeating 3-action pattern (A-B-C-A-B-C) - INTERVENING NOW")
|
|
517
|
+
self._trigger_loop_intervention("Repeating 3-action pattern detected")
|
|
518
|
+
|
|
519
|
+
if self.register_new_step_callback:
|
|
520
|
+
if callable(self.register_new_step_callback):
|
|
521
|
+
self.register_new_step_callback(state, model_output, self.state.n_steps)
|
|
522
|
+
else:
|
|
523
|
+
self.logger.warning("register_new_step_callback is set but not callable")
|
|
524
|
+
|
|
525
|
+
# Offload blocking file I/O off the event loop so concurrent sessions
|
|
526
|
+
# aren't stalled by this session's conversation write.
|
|
527
|
+
await asyncio.to_thread(self._save_conversation, input_messages, model_output)
|
|
528
|
+
self.message_manager.remove_last_state_message() # we dont want the whole state in the chat history
|
|
529
|
+
|
|
530
|
+
self._check_if_stopped()
|
|
531
|
+
|
|
532
|
+
# T-16: Model output was already added earlier, right after get_next_action
|
|
533
|
+
# This ensures consistent flow for both native and non-native tool calls
|
|
534
|
+
# tool_call_id is already set from the earlier add_model_output call
|
|
535
|
+
|
|
536
|
+
async def _call_llm(self, input_messages):
|
|
537
|
+
"""Phase 2: call the LLM and normalize its tool calls -> (model_output, tool_calls_to_pass)."""
|
|
538
|
+
# P2-14: get_next_action consumes one-shot ephemerals (correspondent reply /
|
|
539
|
+
# RECALL) into a pending buffer. Drop them once the LLM responds; on a transient
|
|
540
|
+
# failure, re-queue them so the next step re-includes them instead of losing them.
|
|
541
|
+
try:
|
|
542
|
+
model_output = await self.get_next_action(input_messages)
|
|
543
|
+
except Exception:
|
|
544
|
+
try:
|
|
545
|
+
self.message_manager.restore_ephemeral_on_failure()
|
|
546
|
+
except Exception:
|
|
547
|
+
pass
|
|
548
|
+
raise
|
|
549
|
+
try:
|
|
550
|
+
self.message_manager.commit_ephemeral_consumption()
|
|
551
|
+
except Exception:
|
|
552
|
+
pass
|
|
553
|
+
|
|
554
|
+
# Handle tool_call_id based on native vs non-native mode
|
|
555
|
+
tool_calls_to_pass = None
|
|
556
|
+
if self.message_manager.use_native_tools:
|
|
557
|
+
# Get tool calls stored by get_next_action
|
|
558
|
+
if hasattr(self, '_current_normalized_tool_calls') and self._current_normalized_tool_calls:
|
|
559
|
+
tool_calls_to_pass = self._current_normalized_tool_calls
|
|
560
|
+
self.logger.debug(f"Using {len(tool_calls_to_pass)} native tool calls from LLM response")
|
|
561
|
+
else:
|
|
562
|
+
self.logger.debug("Native tools enabled but no tool calls in LLM response")
|
|
563
|
+
else:
|
|
564
|
+
# For non-native models, create synthetic tool calls from actions
|
|
565
|
+
if model_output and hasattr(model_output, 'action') and model_output.action:
|
|
566
|
+
# Create synthetic tool calls for non-native models
|
|
567
|
+
synthetic_calls = []
|
|
568
|
+
for idx, action in enumerate(model_output.action):
|
|
569
|
+
# Extract action name and params
|
|
570
|
+
if hasattr(action, 'model_dump'):
|
|
571
|
+
action_dict = action.model_dump(exclude_unset=True, by_alias=True)
|
|
572
|
+
if action_dict:
|
|
573
|
+
action_name = list(action_dict.keys())[0]
|
|
574
|
+
action_params = action_dict[action_name] if isinstance(action_dict[action_name], dict) else {}
|
|
575
|
+
else:
|
|
576
|
+
continue
|
|
577
|
+
elif isinstance(action, dict) and action:
|
|
578
|
+
action_name = list(action.keys())[0]
|
|
579
|
+
action_params = action[action_name] if isinstance(action[action_name], dict) else {}
|
|
580
|
+
else:
|
|
581
|
+
continue
|
|
582
|
+
|
|
583
|
+
# Create synthetic tool call
|
|
584
|
+
synthetic_call = {
|
|
585
|
+
'id': f"synthetic_{self.state.n_steps}_{idx}",
|
|
586
|
+
'name': action_name,
|
|
587
|
+
'args': action_params
|
|
588
|
+
}
|
|
589
|
+
synthetic_calls.append(synthetic_call)
|
|
590
|
+
|
|
591
|
+
if synthetic_calls:
|
|
592
|
+
# Normalize them using ToolCallBuilder
|
|
593
|
+
from agents.task.agent.message_manager.tool_call_builder import ToolCallBuilder
|
|
594
|
+
tool_calls_to_pass = [
|
|
595
|
+
ToolCallBuilder.normalize_and_correct(tc, provider="synthetic")
|
|
596
|
+
for tc in synthetic_calls
|
|
597
|
+
]
|
|
598
|
+
|
|
599
|
+
if tool_calls_to_pass:
|
|
600
|
+
self.logger.debug(f"Created {len(tool_calls_to_pass)} synthetic tool calls for non-native model")
|
|
601
|
+
# Store them for tracking
|
|
602
|
+
if self.tool_call_tracker:
|
|
603
|
+
for call in tool_calls_to_pass:
|
|
604
|
+
self.tool_call_tracker.register_call(
|
|
605
|
+
call_id=call.get('id') if isinstance(call, dict) else call.id,
|
|
606
|
+
tool_name=call.get('name') if isinstance(call, dict) else call.name,
|
|
607
|
+
args=call.get('args', {}) if isinstance(call, dict) else getattr(call, 'args', {})
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
# Tool call IDs are now tracked by tool_call_tracker - no temporary storage needed
|
|
611
|
+
|
|
612
|
+
# â
PHASE 2 (Nov 5, 2025): Atomic message addition AFTER execution
|
|
613
|
+
# DON'T add model output yet - wait until after successful execution
|
|
614
|
+
# AIMessage + ToolMessages will be added atomically via add_tool_call_pair_atomic()
|
|
615
|
+
self.logger.debug(f"Prepared {len(tool_calls_to_pass) if tool_calls_to_pass else 0} tool calls for execution")
|
|
616
|
+
# tool_call_id = self.message_manager.add_model_output(model_output, tool_calls=tool_calls_to_pass) # OLD: Optimistic commit
|
|
617
|
+
|
|
618
|
+
# Clean up temporary storage
|
|
619
|
+
if hasattr(self, '_current_normalized_tool_calls'):
|
|
620
|
+
delattr(self, '_current_normalized_tool_calls')
|
|
621
|
+
|
|
622
|
+
return model_output, tool_calls_to_pass
|
|
623
|
+
|
|
624
|
+
async def _finalize_step(self, model_output, state, result, step_info=None):
|
|
625
|
+
"""Finally-phase bookkeeping: complete the tracker step, emit step telemetry, persist memory, auto-save state. No early return."""
|
|
626
|
+
# Ensure tool call tracker step is completed (idempotent - safe to call multiple times)
|
|
627
|
+
# Do this BEFORE telemetry to ensure cleanup happens even if telemetry fails
|
|
628
|
+
# NOTE: complete_step() is already idempotent - it only acts if _current_step_calls is not empty
|
|
629
|
+
# The normal flow (lines 1744, 1749) calls this after adding tool responses,
|
|
630
|
+
# but we call it here too to handle error cases where normal flow didn't complete
|
|
631
|
+
if self.tool_call_tracker:
|
|
632
|
+
try:
|
|
633
|
+
self.tool_call_tracker.complete_step()
|
|
634
|
+
self.logger.debug("Tool call tracker step completed in finally block")
|
|
635
|
+
except Exception as tracker_error:
|
|
636
|
+
self.logger.warning(f"Error completing tool call tracker step: {tracker_error}")
|
|
637
|
+
|
|
638
|
+
actions = await self._emit_step_telemetry(model_output, state, result, step_info)
|
|
639
|
+
|
|
640
|
+
# Add to hierarchical memory (Phase 2)
|
|
641
|
+
if model_output:
|
|
642
|
+
await self._save_step_to_memory(
|
|
643
|
+
step_number=self.state.n_steps,
|
|
644
|
+
brain_state=model_output.current_state.model_dump(),
|
|
645
|
+
actions=actions if actions else [],
|
|
646
|
+
results=result if result else [],
|
|
647
|
+
step_info=step_info
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
# PERSISTENCE: Auto-save agent state and message history periodically
|
|
651
|
+
if self.state.n_steps % 5 == 0:
|
|
652
|
+
try:
|
|
653
|
+
from agents.task.path import pm
|
|
654
|
+
|
|
655
|
+
# Save agent state
|
|
656
|
+
state_file = pm().create_file_path(
|
|
657
|
+
session_id=self.session_id,
|
|
658
|
+
subdir_name="data",
|
|
659
|
+
filename="agent_state.json",
|
|
660
|
+
user_id=self.user_id
|
|
661
|
+
)
|
|
662
|
+
if self.state.save_to_file(state_file):
|
|
663
|
+
self.logger.debug(f"đž Auto-saved agent state at step {self.state.n_steps}")
|
|
664
|
+
|
|
665
|
+
# Save message history (skip for sub-agents - they don't need persistence)
|
|
666
|
+
if hasattr(self, 'message_manager') and self.message_manager and not self._is_sub_agent:
|
|
667
|
+
self.message_manager.save_to_disk(
|
|
668
|
+
session_id=self.session_id,
|
|
669
|
+
user_id=self.user_id
|
|
670
|
+
)
|
|
671
|
+
self.logger.debug(f"đž Auto-saved message history at step {self.state.n_steps}")
|
|
672
|
+
except Exception as save_error:
|
|
673
|
+
self.logger.error(f"Failed to auto-save state at step {self.state.n_steps}: {save_error}")
|
|
674
|
+
|
|
675
|
+
async def _step_impl(self, step_info: Optional[AgentStepInfo] = None) -> None:
|
|
676
|
+
"""Actual step implementation without timeout wrapper"""
|
|
677
|
+
# Phase 1: prepare. Returns the browser_context it acquired so we don't
|
|
678
|
+
# re-acquire it here (that would double-emit telemetry / context tracking).
|
|
679
|
+
state, input_messages, browser_context = await self._prepare_step(step_info)
|
|
680
|
+
|
|
681
|
+
model_output = None
|
|
682
|
+
tool_call_id = None # Initialize here for wider scope
|
|
683
|
+
result: list[ActionResult] = []
|
|
684
|
+
|
|
685
|
+
try:
|
|
686
|
+
model_output, tool_calls_to_pass = await self._call_llm(input_messages)
|
|
687
|
+
|
|
688
|
+
# Validate model output; bail out early if invalid (guidance already injected)
|
|
689
|
+
if not self._validate_and_intervene(model_output):
|
|
690
|
+
# CO-F3: clean up the state message added for this step, same as the
|
|
691
|
+
# exception handlers below do, so a planning-turn / empty-action step
|
|
692
|
+
# doesn't leak its state message into permanent history.
|
|
693
|
+
self.message_manager.remove_last_state_message()
|
|
694
|
+
return
|
|
695
|
+
|
|
696
|
+
result = await self._execute_actions(model_output, tool_calls_to_pass, state, step_info, browser_context)
|
|
697
|
+
result = await self._process_action_results(result, model_output, tool_calls_to_pass)
|
|
698
|
+
if result is None:
|
|
699
|
+
# CO-F3: corrupted tool-message pairing aborted the step; clean up the
|
|
700
|
+
# state message so it doesn't leak into permanent history either.
|
|
701
|
+
self.message_manager.remove_last_state_message()
|
|
702
|
+
return
|
|
703
|
+
await self._record_step(state, model_output, input_messages)
|
|
704
|
+
|
|
705
|
+
except InterruptedError as e:
|
|
706
|
+
# CRITICAL FIX (Dec 2025): Handle stop BEFORE generic Exception handler
|
|
707
|
+
# InterruptedError is a subclass of Exception, so this MUST come first!
|
|
708
|
+
interruption_reason = str(e) if str(e) else "Agent stopped"
|
|
709
|
+
self.logger.info(f'âšī¸ {interruption_reason} at step {self.state.n_steps}')
|
|
710
|
+
|
|
711
|
+
# Also log state flags for debugging
|
|
712
|
+
self.logger.info(f'State flags: stopped={self.state.stopped}')
|
|
713
|
+
|
|
714
|
+
# Clean up state message
|
|
715
|
+
self.message_manager.remove_last_state_message()
|
|
716
|
+
|
|
717
|
+
# Clean up tracker
|
|
718
|
+
if self.tool_call_tracker:
|
|
719
|
+
self.tool_call_tracker.complete_step()
|
|
720
|
+
self.logger.debug("Cleared tool call tracker after agent stopped")
|
|
721
|
+
|
|
722
|
+
# Set graceful stop result - not an error, just user interruption
|
|
723
|
+
self._last_result = [
|
|
724
|
+
ActionResult(
|
|
725
|
+
extracted_content='Agent stopped by user. Send a message to continue.',
|
|
726
|
+
include_in_memory=False # Don't pollute memory with stop messages
|
|
727
|
+
)
|
|
728
|
+
]
|
|
729
|
+
return
|
|
730
|
+
|
|
731
|
+
except Exception as e:
|
|
732
|
+
# Clean up state message for any exception
|
|
733
|
+
self.message_manager.remove_last_state_message()
|
|
734
|
+
|
|
735
|
+
# CRITICAL: Detect fatal errors that should stop execution immediately
|
|
736
|
+
error_str = str(e).lower()
|
|
737
|
+
# HIGH-1: when billing failover is enabled, let billing/quota errors reach
|
|
738
|
+
# _handle_step_error (which tries a provider swap) instead of halting here.
|
|
739
|
+
from agents.task.agent.core.error_recovery import _billing_failover_enabled
|
|
740
|
+
_billing_failover = _billing_failover_enabled()
|
|
741
|
+
is_fatal_error = _is_fatal_step_error(error_str, _billing_failover)
|
|
742
|
+
|
|
743
|
+
if is_fatal_error:
|
|
744
|
+
# Log fatal error and mark session as failed
|
|
745
|
+
self.logger.error(f"â FATAL ERROR - Core component failure, halting session: {e}")
|
|
746
|
+
self.state.consecutive_failures = self.max_failures # Force max failures
|
|
747
|
+
self.state.stopped = True # Mark session as stopped
|
|
748
|
+
|
|
749
|
+
# Create error result for user visibility
|
|
750
|
+
self._last_result = [ActionResult(
|
|
751
|
+
error=f"FATAL ERROR: {str(e)[:400]}. The session has been halted due to a critical error. Please check your API configuration and billing status.",
|
|
752
|
+
include_in_memory=True
|
|
753
|
+
)]
|
|
754
|
+
|
|
755
|
+
# Clean up tracker
|
|
756
|
+
if self.tool_call_tracker:
|
|
757
|
+
self.tool_call_tracker.complete_step()
|
|
758
|
+
|
|
759
|
+
# DO NOT re-raise - just return to let run() loop detect stopped state
|
|
760
|
+
# This prevents infinite retry loops
|
|
761
|
+
return
|
|
762
|
+
|
|
763
|
+
# Non-fatal error - normal error handling
|
|
764
|
+
result = await self._handle_step_error(e)
|
|
765
|
+
self._last_result = result
|
|
766
|
+
# Clean up tracker (no messages added since step failed)
|
|
767
|
+
if self.tool_call_tracker:
|
|
768
|
+
self.tool_call_tracker.complete_step()
|
|
769
|
+
self.logger.debug("Cleared tool call tracker after step error")
|
|
770
|
+
|
|
771
|
+
finally:
|
|
772
|
+
await self._finalize_step(model_output, state, result, step_info)
|
|
773
|
+
|
|
774
|
+
# NEVER `return` inside this finally. A bare `return` here swallows any
|
|
775
|
+
# in-flight exception propagating through it â an asyncio.CancelledError
|
|
776
|
+
# from task.cancel()/the step timeout, or an exception re-raised by
|
|
777
|
+
# _handle_step_error â silently defeating cancellation, the step timeout,
|
|
778
|
+
# and error propagation. Use a positive guard instead of an early return.
|
|
779
|
+
# (Chat-mode step summary removed â handled by telemetry instead.)
|
|
780
|
+
if result and state:
|
|
781
|
+
await self._make_history_item(model_output, state, result)
|