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,398 @@
|
|
|
1
|
+
"""Step validate + execute phases (roadmap P9; code-motion from step.py).
|
|
2
|
+
|
|
3
|
+
The _validate_and_intervene gate, execution-context construction, and the
|
|
4
|
+
_execute_actions phase — split out of StepMixin so step.py drops under 700L while
|
|
5
|
+
keeping the step pipeline readable. Agent composes StepExecutionMixin; _step_impl
|
|
6
|
+
calls these via MRO.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from agents.task.agent.views import ActionResult
|
|
15
|
+
from modules.llm.messages import HumanMessage
|
|
16
|
+
from agents.task.constants import MAX_MCP_PER_STEP
|
|
17
|
+
|
|
18
|
+
#: OR-4: how many consecutive empty-action steps before the strong "thinking loop"
|
|
19
|
+
#: intervention fires. Default 2 (one step sooner than the legacy 3) so a model
|
|
20
|
+
#: that answers in prose without a tool call — Grok/GPT-5 on content-gen — is
|
|
21
|
+
#: pushed to act faster, wasting fewer steps. Set EMPTY_ACTION_FAST_ESCALATE=off
|
|
22
|
+
#: to restore the legacy threshold of 3.
|
|
23
|
+
_FAST_ESCALATE = os.getenv("EMPTY_ACTION_FAST_ESCALATE", "true").strip().lower() not in (
|
|
24
|
+
"0",
|
|
25
|
+
"false",
|
|
26
|
+
"off",
|
|
27
|
+
"no",
|
|
28
|
+
)
|
|
29
|
+
_EMPTY_ACTION_ESCALATE_AT = 2 if _FAST_ESCALATE else 3
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class StepExecutionMixin:
|
|
33
|
+
"""Validate-and-intervene + execute-actions step phases for Agent."""
|
|
34
|
+
|
|
35
|
+
def _validate_and_intervene(self, model_output) -> bool:
|
|
36
|
+
"""Phase 2b: validate model output; inject corrective guidance and return False if the step should end early, else True."""
|
|
37
|
+
# Validate model output
|
|
38
|
+
if not self._validate_model_output(model_output):
|
|
39
|
+
# Inject corrective guidance
|
|
40
|
+
# Initialize thinking loop counter if needed
|
|
41
|
+
if not hasattr(self, '_empty_action_counter'):
|
|
42
|
+
self._empty_action_counter = 0
|
|
43
|
+
|
|
44
|
+
self._empty_action_counter += 1
|
|
45
|
+
self.logger.warning(f"Empty action response {self._empty_action_counter}/{_EMPTY_ACTION_ESCALATE_AT}")
|
|
46
|
+
|
|
47
|
+
# D1-a (seed resolution): tolerate a bounded number of tool-free "planning"
|
|
48
|
+
# turns. Instead of treating the very first empty response as an error,
|
|
49
|
+
# acknowledge it as a planning turn and nudge (not scold) the model to act
|
|
50
|
+
# next. The >=3 thinking-loop escalation below remains the hard backstop, so
|
|
51
|
+
# the loop bound is unchanged. Disable with ALLOWED_REASONING_TURNS=0.
|
|
52
|
+
from agents.task.constants import ALLOWED_REASONING_TURNS
|
|
53
|
+
if self._empty_action_counter <= ALLOWED_REASONING_TURNS:
|
|
54
|
+
self.logger.info(
|
|
55
|
+
f"📝 Tool-free planning turn {self._empty_action_counter}/{ALLOWED_REASONING_TURNS} "
|
|
56
|
+
f"allowed (no action this step; must act next)"
|
|
57
|
+
)
|
|
58
|
+
self.message_manager.inject_user_guidance([{
|
|
59
|
+
'text': (
|
|
60
|
+
"📝 Planning turn noted. You may reason this turn, but you MUST call at "
|
|
61
|
+
"least one function on your NEXT step to make progress "
|
|
62
|
+
"(e.g. done(text=...) if you are finished)."
|
|
63
|
+
),
|
|
64
|
+
'kind': 'guidance',
|
|
65
|
+
'metadata': {'source': 'reasoning_turn_allowance', 'turn': self._empty_action_counter}
|
|
66
|
+
}])
|
|
67
|
+
# Not an error: a planning turn is a legitimate (bounded) outcome.
|
|
68
|
+
# Tagged for telemetry/clarity; the R1 conversational-exit treats it as
|
|
69
|
+
# a non-reply step (it lacks conversational_reply), so it resets the
|
|
70
|
+
# reply-run rather than counting toward an exit.
|
|
71
|
+
self._last_result = [ActionResult(
|
|
72
|
+
extracted_content="Planning turn (no action taken). Act on the next step.",
|
|
73
|
+
include_in_memory=False,
|
|
74
|
+
metadata={'planning_turn': True}
|
|
75
|
+
)]
|
|
76
|
+
if self.tool_call_tracker:
|
|
77
|
+
self.tool_call_tracker.complete_step()
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
# Get available action count for context
|
|
81
|
+
available_actions = len(self.controller.get_action_names()) if self.controller else 0
|
|
82
|
+
|
|
83
|
+
# Escalating intervention after N consecutive failures (OR-4: N defaults
|
|
84
|
+
# to 2 so a prose-only model is pushed to act one step sooner).
|
|
85
|
+
if self._empty_action_counter >= _EMPTY_ACTION_ESCALATE_AT:
|
|
86
|
+
_n = self._empty_action_counter
|
|
87
|
+
self.logger.error(
|
|
88
|
+
f"🚨 CRITICAL: Thinking loop detected - {_n} consecutive steps without function calls"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Strong intervention with explicit example
|
|
92
|
+
self.message_manager.inject_user_guidance([{
|
|
93
|
+
'text': (
|
|
94
|
+
"🚨 INTERVENTION: Thinking loop detected.\n\n"
|
|
95
|
+
f"You have failed to call functions for {_n} consecutive steps.\n"
|
|
96
|
+
"This violates the core agent contract.\n\n"
|
|
97
|
+
"**STOP thinking. START acting.**\n\n"
|
|
98
|
+
"Example for current task:\n"
|
|
99
|
+
f"Task: {self.task}\n\n"
|
|
100
|
+
"If this is a file operation → call filesystem_write_file(...)\n"
|
|
101
|
+
"If this is a search → call mcp_execute_tool(...)\n"
|
|
102
|
+
"If you're stuck → call done(text='explanation')\n\n"
|
|
103
|
+
"**CALL A FUNCTION IN YOUR NEXT RESPONSE.**\n"
|
|
104
|
+
"No more explanations, no more planning."
|
|
105
|
+
),
|
|
106
|
+
'kind': 'intervention',
|
|
107
|
+
'metadata': {
|
|
108
|
+
'source': 'thinking_loop_detector',
|
|
109
|
+
'counter': _n,
|
|
110
|
+
'task': self.task[:100] if self.task else 'unknown'
|
|
111
|
+
}
|
|
112
|
+
}])
|
|
113
|
+
|
|
114
|
+
# History clearing disabled - preserves context
|
|
115
|
+
self.logger.info("Thinking loop detected - guidance injected")
|
|
116
|
+
|
|
117
|
+
# Reset counter for next attempt
|
|
118
|
+
self._empty_action_counter = 0
|
|
119
|
+
|
|
120
|
+
# Set error result
|
|
121
|
+
self._last_result = [ActionResult(
|
|
122
|
+
error="Thinking loop detected - guidance injected. YOU MUST CALL A FUNCTION.",
|
|
123
|
+
include_in_memory=True
|
|
124
|
+
)]
|
|
125
|
+
else:
|
|
126
|
+
# Normal error handling for attempts 1-2
|
|
127
|
+
self.message_manager.inject_user_guidance([
|
|
128
|
+
{
|
|
129
|
+
'text': (
|
|
130
|
+
"❌ CRITICAL ERROR: No function calls detected.\n\n"
|
|
131
|
+
"You MUST call at least one function every step.\n"
|
|
132
|
+
"There is no 'thinking mode' or 'planning phase'.\n\n"
|
|
133
|
+
"**What to do RIGHT NOW:**\n"
|
|
134
|
+
"1. Look at the available functions below\n"
|
|
135
|
+
"2. Pick one that makes progress on the task\n"
|
|
136
|
+
"3. CALL IT with proper parameters\n\n"
|
|
137
|
+
f"You have {available_actions} functions available - use them!"
|
|
138
|
+
),
|
|
139
|
+
'kind': 'error',
|
|
140
|
+
'metadata': {'source': 'empty_actions_validation', 'attempt': self._empty_action_counter}
|
|
141
|
+
}
|
|
142
|
+
])
|
|
143
|
+
|
|
144
|
+
# Set error result
|
|
145
|
+
self._last_result = [ActionResult(
|
|
146
|
+
error=f"No function calls (attempt {self._empty_action_counter}/{_EMPTY_ACTION_ESCALATE_AT})",
|
|
147
|
+
include_in_memory=True
|
|
148
|
+
)]
|
|
149
|
+
|
|
150
|
+
# Clean up tracker (no messages added since execution never happened)
|
|
151
|
+
if self.tool_call_tracker:
|
|
152
|
+
self.tool_call_tracker.complete_step()
|
|
153
|
+
self.logger.debug("Cleared tool call tracker after validation error")
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
# Check if model output has no actions - treat as failure
|
|
157
|
+
if not model_output.action or len(model_output.action) == 0:
|
|
158
|
+
self.logger.error("❌ Model failed to generate actions - this indicates an LLM error")
|
|
159
|
+
self.logger.error("Empty actions violate the agent contract. LLM must provide at least one action per step.")
|
|
160
|
+
|
|
161
|
+
# Treat as a normal failure
|
|
162
|
+
self.state.increment_failures()
|
|
163
|
+
self._last_result = [ActionResult(
|
|
164
|
+
error="LLM failed to generate actions. This may be a prompt issue, API problem, or model limitation.",
|
|
165
|
+
include_in_memory=True
|
|
166
|
+
)]
|
|
167
|
+
if self.tool_call_tracker:
|
|
168
|
+
self.tool_call_tracker.complete_step()
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
def _build_execution_context(self, browser_context):
|
|
174
|
+
"""Build the ActionExecutionContext for this step's action execution."""
|
|
175
|
+
from tools.controller.execution_context import ActionExecutionContext
|
|
176
|
+
# SK-F10: stamp the forged-turn marker (set by _drain_user_messages when the
|
|
177
|
+
# turn was opened by a self-wake / async-delegation-result re-entry, cleared
|
|
178
|
+
# on a genuine drained turn) so action closures (skill_manage,
|
|
179
|
+
# self_context_manage) can tell a forged main-agent turn apart from a
|
|
180
|
+
# genuine one — both otherwise look identical (role='orchestrator',
|
|
181
|
+
# is_sub_agent=False). getattr default is None: an orchestrator that never
|
|
182
|
+
# drained a forged message is genuine.
|
|
183
|
+
turn_kind = getattr(self.orchestrator, '_forged_turn_kind', None)
|
|
184
|
+
execution_context = ActionExecutionContext(
|
|
185
|
+
browser_context=browser_context, # Use local variable from step
|
|
186
|
+
# Agent identification for sub-agent isolation
|
|
187
|
+
agent_id=self.agent_id,
|
|
188
|
+
is_sub_agent=self._is_sub_agent,
|
|
189
|
+
role=getattr(self, '_role', 'orchestrator'),
|
|
190
|
+
parent_session_id=self._parent_session_id if self._is_sub_agent else None,
|
|
191
|
+
# Use effective_session_id (virtual for sub-agents, real for main)
|
|
192
|
+
session_id=self.effective_session_id,
|
|
193
|
+
user_id=self.user_id,
|
|
194
|
+
workspace_dir=self.orchestrator.workspace_dir,
|
|
195
|
+
available_file_paths=self.available_file_paths or [],
|
|
196
|
+
sensitive_data=self.sensitive_data or {},
|
|
197
|
+
metadata={"turn_kind": turn_kind},
|
|
198
|
+
)
|
|
199
|
+
return execution_context
|
|
200
|
+
|
|
201
|
+
async def _execute_actions(self, model_output, tool_calls_to_pass, state, step_info, browser_context):
|
|
202
|
+
"""Phase 3: run the model's actions via the controller and handle results. Returns the result list, or None to signal the step should abort (corrupted state)."""
|
|
203
|
+
# Allow agents to use as many actions as needed per step
|
|
204
|
+
if model_output and hasattr(model_output, 'action') and model_output.action:
|
|
205
|
+
# CRITICAL FIX: Limit MCP actions to prevent timeout cascades
|
|
206
|
+
# MCP tools (scraping, searches, APIs) are expensive and execute SEQUENTIALLY
|
|
207
|
+
actions_list = model_output.action if isinstance(model_output.action, list) else [model_output.action]
|
|
208
|
+
|
|
209
|
+
# Re-queue deferred MCP actions from previous step (if any)
|
|
210
|
+
if self._deferred_mcp_actions:
|
|
211
|
+
self.logger.info(f"🔄 Re-queuing {len(self._deferred_mcp_actions)} deferred MCP actions from previous step")
|
|
212
|
+
# Prepend deferred actions so they execute first
|
|
213
|
+
actions_list = self._deferred_mcp_actions + list(actions_list)
|
|
214
|
+
self._deferred_mcp_actions = []
|
|
215
|
+
# CO-F2: make the merged list authoritative — execution reads
|
|
216
|
+
# model_output.action, which is otherwise only rebuilt when the
|
|
217
|
+
# MCP throttle re-fires, silently dropping the re-queued actions.
|
|
218
|
+
model_output.action = actions_list
|
|
219
|
+
|
|
220
|
+
# Analyze action composition
|
|
221
|
+
mcp_actions = []
|
|
222
|
+
non_mcp_actions = []
|
|
223
|
+
|
|
224
|
+
for action in actions_list:
|
|
225
|
+
action_dump = action.model_dump(exclude_unset=True) if hasattr(action, 'model_dump') else {}
|
|
226
|
+
action_keys = list(action_dump.keys())
|
|
227
|
+
action_name = action_keys[0] if action_keys else None
|
|
228
|
+
|
|
229
|
+
if action_name and action_name.startswith('mcp_execute_tool'):
|
|
230
|
+
mcp_actions.append(action)
|
|
231
|
+
else:
|
|
232
|
+
non_mcp_actions.append(action)
|
|
233
|
+
|
|
234
|
+
# Apply MCP throttling to prevent timeout cascades
|
|
235
|
+
# Uses centralized constant from agents.task.constants
|
|
236
|
+
|
|
237
|
+
if len(mcp_actions) > MAX_MCP_PER_STEP:
|
|
238
|
+
self.logger.warning(
|
|
239
|
+
f"⚠️ Step has {len(mcp_actions)} MCP actions - limiting to {MAX_MCP_PER_STEP} "
|
|
240
|
+
f"to prevent timeout cascades (MCP actions execute sequentially, ~30-180s each)"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Keep first MAX_MCP_PER_STEP MCP actions
|
|
244
|
+
kept_mcp = mcp_actions[:MAX_MCP_PER_STEP]
|
|
245
|
+
deferred_mcp = mcp_actions[MAX_MCP_PER_STEP:]
|
|
246
|
+
|
|
247
|
+
# Save deferred actions for next step (up to max deferrals)
|
|
248
|
+
# Track deferral count via _deferral_count attribute on action
|
|
249
|
+
for action in deferred_mcp:
|
|
250
|
+
if not hasattr(action, '_deferral_count'):
|
|
251
|
+
action._deferral_count = 0
|
|
252
|
+
action._deferral_count += 1
|
|
253
|
+
|
|
254
|
+
if action._deferral_count <= self._max_mcp_deferrals:
|
|
255
|
+
self._deferred_mcp_actions.append(action)
|
|
256
|
+
else:
|
|
257
|
+
action_dump = action.model_dump(exclude_unset=True) if hasattr(action, 'model_dump') else {}
|
|
258
|
+
action_name = list(action_dump.keys())[0] if action_dump else "unknown"
|
|
259
|
+
self.logger.warning(
|
|
260
|
+
f"⚠️ Dropping MCP action '{action_name}' after {self._max_mcp_deferrals} deferrals"
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Log what's being executed vs deferred
|
|
264
|
+
self.logger.info(
|
|
265
|
+
f"Executing {len(kept_mcp)} MCP + {len(non_mcp_actions)} other actions this step. "
|
|
266
|
+
f"Deferred {len(self._deferred_mcp_actions)} MCP actions to next step."
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Reconstruct action list: kept MCP + all non-MCP
|
|
270
|
+
model_output.action = kept_mcp + non_mcp_actions
|
|
271
|
+
actions_list = model_output.action
|
|
272
|
+
|
|
273
|
+
action_count = len(actions_list)
|
|
274
|
+
self.logger.info(f"Executing {action_count} actions in this step")
|
|
275
|
+
|
|
276
|
+
# VALIDATION: Check that all requested actions exist before execution
|
|
277
|
+
invalid_actions = []
|
|
278
|
+
for action in actions_list:
|
|
279
|
+
action_dump = action.model_dump(exclude_unset=True) if hasattr(action, 'model_dump') else {}
|
|
280
|
+
action_keys = list(action_dump.keys())
|
|
281
|
+
action_name = action_keys[0] if action_keys else None
|
|
282
|
+
|
|
283
|
+
if action_name and not self.controller.has_action(action_name):
|
|
284
|
+
invalid_actions.append(action_name)
|
|
285
|
+
|
|
286
|
+
if invalid_actions:
|
|
287
|
+
error_msg = (
|
|
288
|
+
f"Agent requested invalid actions: {invalid_actions}. "
|
|
289
|
+
f"These actions are not registered in the controller. "
|
|
290
|
+
f"Available actions: {len(self.controller.registry.list_action_names())}"
|
|
291
|
+
)
|
|
292
|
+
self.logger.error(f"❌ {error_msg}")
|
|
293
|
+
raise ValueError(error_msg)
|
|
294
|
+
|
|
295
|
+
# Issue #8 Fix: Pre-execution MCP validation loop check
|
|
296
|
+
# Block MCP actions that have repeatedly failed validation (saves resources)
|
|
297
|
+
if mcp_actions and self.tool_call_tracker:
|
|
298
|
+
blocked_mcp = []
|
|
299
|
+
allowed_mcp = []
|
|
300
|
+
|
|
301
|
+
for action in mcp_actions:
|
|
302
|
+
action_dump = action.model_dump(exclude_unset=True) if hasattr(action, 'model_dump') else {}
|
|
303
|
+
mcp_params = action_dump.get('mcp_execute_tool', {})
|
|
304
|
+
server_name = mcp_params.get('server_name', '')
|
|
305
|
+
tool_name = mcp_params.get('tool_name', '')
|
|
306
|
+
|
|
307
|
+
if server_name and tool_name:
|
|
308
|
+
if self.tool_call_tracker.should_inject_mcp_schema(server_name, tool_name):
|
|
309
|
+
blocked_mcp.append((server_name, tool_name))
|
|
310
|
+
self.logger.warning(
|
|
311
|
+
f"🚫 Blocking repeated MCP validation failure: {server_name}/{tool_name}. "
|
|
312
|
+
f"Check the error messages above for correct parameter format."
|
|
313
|
+
)
|
|
314
|
+
else:
|
|
315
|
+
allowed_mcp.append(action)
|
|
316
|
+
else:
|
|
317
|
+
allowed_mcp.append(action)
|
|
318
|
+
|
|
319
|
+
if blocked_mcp:
|
|
320
|
+
# Update the MCP actions list to only include allowed ones
|
|
321
|
+
mcp_actions = allowed_mcp
|
|
322
|
+
model_output.action = mcp_actions + non_mcp_actions
|
|
323
|
+
actions_list = model_output.action
|
|
324
|
+
|
|
325
|
+
# Inject guidance
|
|
326
|
+
blocked_names = [f"{s}/{t}" for s, t in blocked_mcp]
|
|
327
|
+
self.message_manager.inject_user_guidance([{
|
|
328
|
+
'text': (
|
|
329
|
+
f"⚠️ Blocked {len(blocked_mcp)} MCP actions due to repeated validation failures: {blocked_names}\n\n"
|
|
330
|
+
f"The schema has been injected in previous error messages. "
|
|
331
|
+
f"Please review the FULL SCHEMA and correct your arguments before retrying."
|
|
332
|
+
),
|
|
333
|
+
'kind': 'warning',
|
|
334
|
+
'metadata': {'source': 'mcp_validation_loop_prevention'}
|
|
335
|
+
}])
|
|
336
|
+
|
|
337
|
+
# PHASE 2 FIX (Nov 4, 2025): Loop Detection
|
|
338
|
+
# Detect if agent is repeating same actions without progress
|
|
339
|
+
if model_output and hasattr(model_output, 'current_state') and model_output.current_state:
|
|
340
|
+
loop_detected, loop_warning = self.detect_action_loop(
|
|
341
|
+
model_output.current_state,
|
|
342
|
+
actions_list
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
if loop_detected and loop_warning:
|
|
346
|
+
# Log warning
|
|
347
|
+
self.logger.warning(f"⚠️ Loop detected at step {self.state.n_steps}")
|
|
348
|
+
|
|
349
|
+
# Inject warning into agent's next context as ephemeral message
|
|
350
|
+
# HumanMessage imported at module level from modules.llm.messages
|
|
351
|
+
self.message_manager.push_ephemeral_message(
|
|
352
|
+
HumanMessage(content=loop_warning)
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# Optional: Could halt after N consecutive loops
|
|
356
|
+
# For now: Non-blocking, agent gets feedback and can adjust
|
|
357
|
+
|
|
358
|
+
# CRITICAL FIX: Actually execute the actions through controller
|
|
359
|
+
execution_context = self._build_execution_context(browser_context)
|
|
360
|
+
|
|
361
|
+
# FIX (Jan 2026): Tool call rate limiting to prevent context overflow
|
|
362
|
+
# Limit parallel tool calls to prevent 60K+ token additions in a single step
|
|
363
|
+
MAX_TOOL_CALLS_PER_STEP = 3 # Allow up to 3 parallel calls
|
|
364
|
+
actions_to_execute = model_output.action
|
|
365
|
+
deferred_actions = []
|
|
366
|
+
|
|
367
|
+
if len(actions_to_execute) > MAX_TOOL_CALLS_PER_STEP:
|
|
368
|
+
self.logger.warning(
|
|
369
|
+
f"⚠️ Tool limit: {len(actions_to_execute)} actions requested, "
|
|
370
|
+
f"executing first {MAX_TOOL_CALLS_PER_STEP}, deferring {len(actions_to_execute) - MAX_TOOL_CALLS_PER_STEP}"
|
|
371
|
+
)
|
|
372
|
+
deferred_actions = actions_to_execute[MAX_TOOL_CALLS_PER_STEP:]
|
|
373
|
+
actions_to_execute = actions_to_execute[:MAX_TOOL_CALLS_PER_STEP]
|
|
374
|
+
|
|
375
|
+
# Inject deferral notice as ephemeral message for next step
|
|
376
|
+
deferred_names = [getattr(a, 'name', str(a)[:30]) for a in deferred_actions]
|
|
377
|
+
self.message_manager.push_ephemeral_message(
|
|
378
|
+
HumanMessage(content=(
|
|
379
|
+
f"⚠️ **Tool Call Limit Applied**\n\n"
|
|
380
|
+
f"{len(deferred_actions)} tool calls were deferred to prevent context overflow:\n"
|
|
381
|
+
f"- {', '.join(deferred_names[:5])}"
|
|
382
|
+
f"{'...' if len(deferred_names) > 5 else ''}\n\n"
|
|
383
|
+
f"Process these in your next step, 1-3 at a time."
|
|
384
|
+
))
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
result = await self.controller.multi_act(
|
|
388
|
+
actions=actions_to_execute,
|
|
389
|
+
execution_context=execution_context
|
|
390
|
+
)
|
|
391
|
+
self._last_result = result
|
|
392
|
+
# L3: count the actions actually executed, not the pre-truncation request count
|
|
393
|
+
# (a step requesting >MAX_TOOL_CALLS_PER_STEP defers the rest — action_count
|
|
394
|
+
# would over-count what ran, feeding stall detection / telemetry a wrong number).
|
|
395
|
+
self.state.total_actions_count += len(actions_to_execute)
|
|
396
|
+
|
|
397
|
+
return result
|
|
398
|
+
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Step-telemetry mixin (roadmap P9; code-motion from step.py).
|
|
2
|
+
|
|
3
|
+
The best-effort per-step + iteration-complete telemetry emission, split out of the
|
|
4
|
+
StepMixin so step.py stays focused on the step phases. Agent composes
|
|
5
|
+
StepTelemetryMixin; the only caller is _finalize_step (via MRO).
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _detect_service_for_action(action_name: str) -> str:
|
|
11
|
+
"""Lazy delegate to the module-level helper in service.py.
|
|
12
|
+
|
|
13
|
+
Defined here so the moved telemetry body (which references
|
|
14
|
+
``_detect_service_for_action`` as a global) resolves at runtime without a
|
|
15
|
+
circular import at module load time. Mirrors the shim in ``step.py``.
|
|
16
|
+
"""
|
|
17
|
+
from agents.task.agent.service import _detect_service_for_action as _impl
|
|
18
|
+
return _impl(action_name)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class StepTelemetryMixin:
|
|
22
|
+
"""Per-step telemetry emission for Agent."""
|
|
23
|
+
|
|
24
|
+
async def _emit_step_telemetry(self, model_output, state, result, step_info=None):
|
|
25
|
+
"""Best-effort step + iteration-complete telemetry; returns extracted actions for memory persistence."""
|
|
26
|
+
actions = []
|
|
27
|
+
try: # Capture telemetry using TelemetryManager
|
|
28
|
+
import time as time_module
|
|
29
|
+
step_end_time = time_module.time()
|
|
30
|
+
|
|
31
|
+
# Extract actions from model output
|
|
32
|
+
if model_output and model_output.action is not None:
|
|
33
|
+
for a in model_output.action:
|
|
34
|
+
if hasattr(a, 'model_dump'):
|
|
35
|
+
actions.append(a.model_dump(exclude_unset=True))
|
|
36
|
+
elif isinstance(a, dict):
|
|
37
|
+
actions.append(a)
|
|
38
|
+
else:
|
|
39
|
+
actions.append({'unknown': str(a)})
|
|
40
|
+
|
|
41
|
+
# Extract errors from results
|
|
42
|
+
errors = [r.error for r in result if r.error] if result else []
|
|
43
|
+
|
|
44
|
+
# Create enhanced input data
|
|
45
|
+
inputs = {
|
|
46
|
+
"task": self.task,
|
|
47
|
+
"state": {
|
|
48
|
+
"url": state.url if state else None,
|
|
49
|
+
"title": state.title if state else None,
|
|
50
|
+
},
|
|
51
|
+
"last_result": [r.error or r.extracted_content for r in self._last_result] if self._last_result else []
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Create enhanced output data
|
|
55
|
+
outputs = {}
|
|
56
|
+
if model_output:
|
|
57
|
+
outputs = {
|
|
58
|
+
"page_summary": model_output.current_state.page_summary if hasattr(model_output.current_state, "page_summary") else "",
|
|
59
|
+
"evaluation_previous_goal": model_output.current_state.evaluation_previous_goal if hasattr(model_output.current_state, "evaluation_previous_goal") else "",
|
|
60
|
+
"memory": model_output.current_state.memory if hasattr(model_output.current_state, "memory") else "",
|
|
61
|
+
"next_goal": model_output.current_state.next_goal if hasattr(model_output.current_state, "next_goal") else "",
|
|
62
|
+
"reasoning": model_output.current_state.reasoning if hasattr(model_output.current_state, "reasoning") else ""
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Capture metrics
|
|
66
|
+
metrics = {
|
|
67
|
+
"consecutive_failures": self.state.consecutive_failures,
|
|
68
|
+
"token_count": self.message_manager.get_token_count() if hasattr(self.message_manager, "get_token_count") else None
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# Extract file operations from actions and results
|
|
72
|
+
files_created = []
|
|
73
|
+
files_modified = []
|
|
74
|
+
files_read = []
|
|
75
|
+
files_deleted = []
|
|
76
|
+
|
|
77
|
+
for action in actions:
|
|
78
|
+
action_name = None
|
|
79
|
+
action_params = {}
|
|
80
|
+
|
|
81
|
+
# Handle different action formats
|
|
82
|
+
if isinstance(action, dict):
|
|
83
|
+
# Format: {action_name: {params}} or {name: x, params: y}
|
|
84
|
+
if 'name' in action:
|
|
85
|
+
action_name = action.get('name')
|
|
86
|
+
action_params = action.get('params', action)
|
|
87
|
+
else:
|
|
88
|
+
# First key is action name
|
|
89
|
+
keys = [k for k in action.keys() if k not in ('status', 'error')]
|
|
90
|
+
if keys:
|
|
91
|
+
action_name = keys[0]
|
|
92
|
+
action_params = action.get(action_name, {}) if isinstance(action.get(action_name), dict) else {}
|
|
93
|
+
|
|
94
|
+
if action_name:
|
|
95
|
+
action_name_lower = action_name.lower()
|
|
96
|
+
file_path = action_params.get('file_path') or action_params.get('path') or action_params.get('filename')
|
|
97
|
+
|
|
98
|
+
if file_path:
|
|
99
|
+
if action_name_lower in ('write_file', 'create_file', 'save_file'):
|
|
100
|
+
files_created.append(file_path)
|
|
101
|
+
elif action_name_lower in ('append_file', 'edit_file', 'modify_file', 'update_file'):
|
|
102
|
+
files_modified.append(file_path)
|
|
103
|
+
elif action_name_lower in ('read_file', 'get_file_content', 'load_file'):
|
|
104
|
+
files_read.append(file_path)
|
|
105
|
+
elif action_name_lower in ('delete_file', 'remove_file'):
|
|
106
|
+
files_deleted.append(file_path)
|
|
107
|
+
|
|
108
|
+
# Determine iteration type based on actions
|
|
109
|
+
iteration_type = "mixed"
|
|
110
|
+
if not actions:
|
|
111
|
+
iteration_type = "thinking"
|
|
112
|
+
else:
|
|
113
|
+
services = set()
|
|
114
|
+
has_done = False
|
|
115
|
+
for action in actions:
|
|
116
|
+
if isinstance(action, dict):
|
|
117
|
+
# Check for done action
|
|
118
|
+
if 'done' in action or action.get('name') == 'done':
|
|
119
|
+
has_done = True
|
|
120
|
+
# Detect service
|
|
121
|
+
action_name = action.get('name') or list(action.keys())[0] if action else None
|
|
122
|
+
if action_name:
|
|
123
|
+
service = _detect_service_for_action(action_name)
|
|
124
|
+
services.add(service)
|
|
125
|
+
|
|
126
|
+
if has_done:
|
|
127
|
+
iteration_type = "done"
|
|
128
|
+
elif services == {'browser'}:
|
|
129
|
+
iteration_type = "browser"
|
|
130
|
+
elif services == {'filesystem'}:
|
|
131
|
+
iteration_type = "filesystem"
|
|
132
|
+
elif services == {'mcp'}:
|
|
133
|
+
iteration_type = "mcp"
|
|
134
|
+
|
|
135
|
+
# Determine iteration status
|
|
136
|
+
iteration_status = "completed"
|
|
137
|
+
error_message = None
|
|
138
|
+
if errors:
|
|
139
|
+
if len(errors) == len(actions) and actions:
|
|
140
|
+
iteration_status = "failed"
|
|
141
|
+
else:
|
|
142
|
+
iteration_status = "partial"
|
|
143
|
+
error_message = errors[0] if errors else None
|
|
144
|
+
|
|
145
|
+
# Check for done action
|
|
146
|
+
is_done = any(
|
|
147
|
+
isinstance(a, dict) and ('done' in a or a.get('name') == 'done')
|
|
148
|
+
for a in actions
|
|
149
|
+
) if actions else False
|
|
150
|
+
|
|
151
|
+
if is_done:
|
|
152
|
+
iteration_status = "done"
|
|
153
|
+
|
|
154
|
+
# Use TelemetryManager - single method call with new iteration fields
|
|
155
|
+
self.telemetry_manager.capture_step(
|
|
156
|
+
step=self.state.n_steps,
|
|
157
|
+
actions=actions,
|
|
158
|
+
errors=errors,
|
|
159
|
+
brain_state=model_output.current_state if model_output else None,
|
|
160
|
+
consecutive_failures=self.state.consecutive_failures,
|
|
161
|
+
agent_name=getattr(self, 'agent_name', 'Unknown'),
|
|
162
|
+
agent_type=self.__class__.__name__,
|
|
163
|
+
current_task=self.task,
|
|
164
|
+
inputs=inputs,
|
|
165
|
+
outputs=outputs,
|
|
166
|
+
metrics=metrics,
|
|
167
|
+
# New iteration fields
|
|
168
|
+
iteration=self.state.n_steps,
|
|
169
|
+
iteration_type=iteration_type,
|
|
170
|
+
iteration_status=iteration_status,
|
|
171
|
+
files_created=files_created,
|
|
172
|
+
files_modified=files_modified,
|
|
173
|
+
files_read=files_read,
|
|
174
|
+
files_deleted=files_deleted,
|
|
175
|
+
error_message=error_message,
|
|
176
|
+
is_done=is_done
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Also emit iteration_complete event for clean UI boundary
|
|
180
|
+
reasoning_summary = ""
|
|
181
|
+
if model_output and hasattr(model_output.current_state, 'reasoning'):
|
|
182
|
+
reasoning_summary = (model_output.current_state.reasoning or "")[:200]
|
|
183
|
+
|
|
184
|
+
# Build action results summary
|
|
185
|
+
action_results = []
|
|
186
|
+
if result:
|
|
187
|
+
for i, r in enumerate(result):
|
|
188
|
+
action_results.append({
|
|
189
|
+
'index': i,
|
|
190
|
+
'success': not r.error,
|
|
191
|
+
'error': r.error,
|
|
192
|
+
'has_content': bool(r.extracted_content)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
# Calculate duration using state's last_step_start_time
|
|
196
|
+
step_duration = 0.0
|
|
197
|
+
if hasattr(self.state, 'last_step_start_time') and self.state.last_step_start_time:
|
|
198
|
+
step_duration = step_end_time - self.state.last_step_start_time
|
|
199
|
+
|
|
200
|
+
self.telemetry_manager.capture_iteration_complete(
|
|
201
|
+
iteration=self.state.n_steps,
|
|
202
|
+
step=self.state.n_steps,
|
|
203
|
+
iteration_type=iteration_type,
|
|
204
|
+
iteration_status=iteration_status,
|
|
205
|
+
reasoning_summary=reasoning_summary,
|
|
206
|
+
actions_executed=[], # Don't duplicate actions - step event already has them
|
|
207
|
+
action_results=action_results,
|
|
208
|
+
files_created=files_created,
|
|
209
|
+
files_modified=files_modified,
|
|
210
|
+
files_read=files_read,
|
|
211
|
+
files_deleted=files_deleted,
|
|
212
|
+
success=(iteration_status in ('completed', 'done')),
|
|
213
|
+
error=error_message,
|
|
214
|
+
is_done=is_done,
|
|
215
|
+
duration_seconds=step_duration
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
except Exception as telemetry_error:
|
|
219
|
+
self.logger.debug(f"Failed to capture step telemetry: {telemetry_error}", exc_info=True)
|
|
220
|
+
|
|
221
|
+
return actions
|
|
222
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Tests for the aux-LLM metering helper (A3).
|
|
2
|
+
|
|
3
|
+
Covers: normal metering call records with the right component/tokens; no-op when
|
|
4
|
+
no user_id/tracker; fail-open (never raises) on a tracker error including anything
|
|
5
|
+
that looks like InsufficientCreditsError.
|
|
6
|
+
"""
|
|
7
|
+
import asyncio
|
|
8
|
+
from types import SimpleNamespace
|
|
9
|
+
|
|
10
|
+
from agents.task.agent.core.aux_metering import meter_aux_llm
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _Tracker:
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.calls = []
|
|
16
|
+
|
|
17
|
+
async def record_llm_usage(self, **kw):
|
|
18
|
+
self.calls.append(kw)
|
|
19
|
+
return SimpleNamespace()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_meter_aux_records_with_component_and_tokens():
|
|
23
|
+
tr = _Tracker()
|
|
24
|
+
llm = SimpleNamespace(model_name="claude-haiku-4-5")
|
|
25
|
+
resp = SimpleNamespace(usage_metadata={"input_tokens": 500, "output_tokens": 40})
|
|
26
|
+
asyncio.run(meter_aux_llm(usage_tracker=tr, user_id="u1", session_id="s1",
|
|
27
|
+
agent_id="a1", llm=llm, response=resp, duration_seconds=0.1,
|
|
28
|
+
component="compaction", purpose="compaction"))
|
|
29
|
+
assert len(tr.calls) == 1
|
|
30
|
+
c = tr.calls[0]
|
|
31
|
+
assert c["component"] == "compaction" and c["input_tokens"] == 500 and c["output_tokens"] == 40
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_meter_aux_noop_without_user():
|
|
35
|
+
tr = _Tracker()
|
|
36
|
+
asyncio.run(meter_aux_llm(usage_tracker=tr, user_id=None, session_id="s", agent_id="a",
|
|
37
|
+
llm=SimpleNamespace(), response=SimpleNamespace(), duration_seconds=0,
|
|
38
|
+
component="judge", purpose="x"))
|
|
39
|
+
assert tr.calls == []
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_meter_aux_fails_open_on_tracker_error():
|
|
43
|
+
class Boom:
|
|
44
|
+
async def record_llm_usage(self, **kw):
|
|
45
|
+
raise RuntimeError("insufficient")
|
|
46
|
+
|
|
47
|
+
# must NOT raise
|
|
48
|
+
asyncio.run(meter_aux_llm(usage_tracker=Boom(), user_id="u", session_id="s", agent_id="a",
|
|
49
|
+
llm=SimpleNamespace(model_name="m"), response=SimpleNamespace(usage_metadata={}),
|
|
50
|
+
duration_seconds=0, component="reflection", purpose="x"))
|