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,1179 @@
|
|
|
1
|
+
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import base64
|
|
6
|
+
import http
|
|
7
|
+
import importlib
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
import subprocess
|
|
13
|
+
import traceback
|
|
14
|
+
import time
|
|
15
|
+
from dataclasses import dataclass, field
|
|
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
|
+
def _resolve_generate_gif(config_value, gif_enabled: bool):
|
|
35
|
+
"""Gate GIF creation behind the ENABLE_GIF_CREATION operator flag (live-test F-GIF).
|
|
36
|
+
|
|
37
|
+
The flag (default false) is the master switch. When OFF, suppress GIF creation
|
|
38
|
+
entirely — a headless/autonomous server has no UI consumer for them. When ON,
|
|
39
|
+
honor the config value (a bool, or a str output path).
|
|
40
|
+
"""
|
|
41
|
+
return config_value if gif_enabled else False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _bump_eager_skill_usage(matched_skills, user_id, logger) -> None:
|
|
45
|
+
"""SK-F3: record a provenance load for every eager-injected skill.
|
|
46
|
+
|
|
47
|
+
With SKILL_PROGRESSIVE_DISCLOSURE off, skills are embedded full-body directly
|
|
48
|
+
into the system message and the agent never calls ``load_skill`` — the only
|
|
49
|
+
other place usage is bumped (``action_registration.py``'s ``load_skill``
|
|
50
|
+
closure). Without this, the curator (W5) sees zero loads for an authored
|
|
51
|
+
skill that IS actively guiding the agent every turn and archives it as
|
|
52
|
+
unused. Pure module-level function so it's unit-testable without
|
|
53
|
+
constructing a full ``Agent``. Fail-open — a metrics write must never break
|
|
54
|
+
skill injection.
|
|
55
|
+
"""
|
|
56
|
+
if not user_id:
|
|
57
|
+
return
|
|
58
|
+
try:
|
|
59
|
+
from modules.skills.skill_usage import get_skill_usage_store
|
|
60
|
+
_usage_store = get_skill_usage_store()
|
|
61
|
+
for _s in matched_skills:
|
|
62
|
+
_usage_store.bump_load(_s.skill_id, user_id)
|
|
63
|
+
except Exception as _bump_err:
|
|
64
|
+
logger.debug(f"eager skill usage bump skipped (non-fatal): {_bump_err}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Import POLYROB exceptions
|
|
68
|
+
from core.exceptions import (
|
|
69
|
+
AgentError,
|
|
70
|
+
ValidationError as ROBValidationError,
|
|
71
|
+
LLMResponseError,
|
|
72
|
+
ToolError
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Import billing exception for fail-fast handling
|
|
76
|
+
from core.exceptions import InsufficientCreditsError
|
|
77
|
+
|
|
78
|
+
# Import centralized utilities to avoid repeated inline imports
|
|
79
|
+
from agents.task.utils_json import normalize_action_schema
|
|
80
|
+
# Model limits now come from modules.llm.model_registry
|
|
81
|
+
|
|
82
|
+
# Native message types
|
|
83
|
+
from modules.llm.messages import (
|
|
84
|
+
AIMessage,
|
|
85
|
+
BaseMessage,
|
|
86
|
+
HumanMessage,
|
|
87
|
+
SystemMessage,
|
|
88
|
+
ToolMessage,
|
|
89
|
+
MessageOrigin,
|
|
90
|
+
make_control_message,
|
|
91
|
+
)
|
|
92
|
+
from modules.llm.adapters import BaseChatModel
|
|
93
|
+
from core.exceptions import (
|
|
94
|
+
RateLimitError,
|
|
95
|
+
LLMError,
|
|
96
|
+
LLMRateLimitError,
|
|
97
|
+
LLMAuthenticationError,
|
|
98
|
+
LLMConnectionError,
|
|
99
|
+
LLMContextLengthError,
|
|
100
|
+
LLMResponseError,
|
|
101
|
+
LLMPermanentError,
|
|
102
|
+
LLMProviderExhaustedError
|
|
103
|
+
)
|
|
104
|
+
# PIL Image imported locally in save_screenshot() method where needed
|
|
105
|
+
from pydantic import BaseModel, ConfigDict, ValidationError
|
|
106
|
+
|
|
107
|
+
from tools.browser.views import BrowserStateHistory, BrowserState
|
|
108
|
+
from agents.task.agent.message_manager.service import MessageManager
|
|
109
|
+
from agents.task.agent.core.llm_runner import LLMRunnerMixin
|
|
110
|
+
from agents.task.agent.core.memory_writer import MemoryWriterMixin
|
|
111
|
+
from agents.task.agent.core.step import StepMixin
|
|
112
|
+
from agents.task.agent.core.history_io import HistoryIOMixin
|
|
113
|
+
from agents.task.agent.core.logging_io import LoggingIOMixin
|
|
114
|
+
from agents.task.agent.core.safety_lifecycle import SafetyLifecycleMixin
|
|
115
|
+
from agents.task.agent.core.user_ingress import UserIngressMixin
|
|
116
|
+
from agents.task.agent.core.llm_provisioning import LLMProvisioningMixin
|
|
117
|
+
from agents.task.agent.core.model_introspection import ModelIntrospectionMixin
|
|
118
|
+
from agents.task.agent.core.loop_detection import LoopDetectionMixin
|
|
119
|
+
from agents.task.agent.core.resources import ResourceMixin
|
|
120
|
+
from agents.task.agent.core.session_metadata import SessionMetadataMixin
|
|
121
|
+
from agents.task.agent.core.error_recovery import ErrorRecoveryMixin
|
|
122
|
+
from agents.task.agent.core.step_telemetry import StepTelemetryMixin
|
|
123
|
+
from agents.task.agent.core.output_validation import OutputValidationMixin
|
|
124
|
+
from agents.task.agent.core.result_processing import ResultProcessingMixin
|
|
125
|
+
from agents.task.agent.core.next_action_internal import NextActionInternalMixin
|
|
126
|
+
from agents.task.agent.core.step_execution import StepExecutionMixin
|
|
127
|
+
from agents.task.agent.core.run_loop import RunLoopMixin
|
|
128
|
+
from agents.task.agent.prompts import SystemPrompt, AgentMessagePrompt
|
|
129
|
+
from agents.task.agent.views import (
|
|
130
|
+
ActionModel,
|
|
131
|
+
AgentBrain,
|
|
132
|
+
AgentError,
|
|
133
|
+
AgentHistory,
|
|
134
|
+
AgentHistoryList,
|
|
135
|
+
AgentOutput,
|
|
136
|
+
AgentStepInfo,
|
|
137
|
+
ActionResult,
|
|
138
|
+
)
|
|
139
|
+
from tools.browser.context import BrowserContext
|
|
140
|
+
from tools.dom.views import DOMElementNode, SelectorMap
|
|
141
|
+
from agents.task.telemetry.views import (
|
|
142
|
+
HumanApprovalRequestedEvent,
|
|
143
|
+
HumanApprovalDecisionEvent,
|
|
144
|
+
TodoStatusEvent,
|
|
145
|
+
AgentRunTelemetryEvent,
|
|
146
|
+
AgentEndTelemetryEvent,
|
|
147
|
+
ProviderFailureEvent,
|
|
148
|
+
ProviderFallbackSuccessEvent,
|
|
149
|
+
)
|
|
150
|
+
# ProductTelemetry no longer directly used - accessed via TelemetryManager
|
|
151
|
+
from agents.task.utils import time_execution_async, detect_llm_provider, extract_token_usage
|
|
152
|
+
# Safely import Google API exceptions
|
|
153
|
+
try:
|
|
154
|
+
from google.api_core.exceptions import ResourceExhausted
|
|
155
|
+
except ImportError:
|
|
156
|
+
# Create a dummy exception class if google-api-core is not installed
|
|
157
|
+
class ResourceExhausted(Exception):
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
# Import from our own logging config
|
|
161
|
+
from agents.task.logging_config import get_task_logger
|
|
162
|
+
|
|
163
|
+
# Import centralized path management
|
|
164
|
+
from agents.task.path import pm
|
|
165
|
+
|
|
166
|
+
load_dotenv()
|
|
167
|
+
|
|
168
|
+
# Generic logger for the module itself (not instances)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def resolve_profile_overrides(updated_config, *, tool_calling_method,
|
|
172
|
+
max_actions_per_step, max_input_tokens, max_failures):
|
|
173
|
+
"""Resolve profile overrides onto the four agent scalars they may set.
|
|
174
|
+
|
|
175
|
+
Replaces the old ``for key in [...]: locals()[key] = updated_config[key]`` loop,
|
|
176
|
+
which was a Python no-op (assigning to ``locals()`` does not rebind locals), so
|
|
177
|
+
these profile overrides were silently dropped. A present key in ``updated_config``
|
|
178
|
+
wins; otherwise the current value is kept. Returns the four values in fixed order.
|
|
179
|
+
"""
|
|
180
|
+
return (
|
|
181
|
+
updated_config.get("tool_calling_method", tool_calling_method),
|
|
182
|
+
updated_config.get("max_actions_per_step", max_actions_per_step),
|
|
183
|
+
updated_config.get("max_input_tokens", max_input_tokens),
|
|
184
|
+
updated_config.get("max_failures", max_failures),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class AgentConstructionMixin:
|
|
189
|
+
"""Agent.__init__ (the ~660L constructor) split whole out of the Agent class so
|
|
190
|
+
service.py drops under 700L (P9). Agent inherits __init__ from this mixin; from_params
|
|
191
|
+
(in service.py) still builds AgentConfig/AgentDeps and calls cls(config, deps). Imports
|
|
192
|
+
above are service.py's full set so every name the constructor references resolves."""
|
|
193
|
+
|
|
194
|
+
def __init__(self, config: AgentConfig, deps: AgentDeps):
|
|
195
|
+
"""Initialize Agent from a config + deps pair.
|
|
196
|
+
|
|
197
|
+
The original 31-param constructor body is preserved verbatim below; the
|
|
198
|
+
parameters are simply unpacked from config/deps into the same local names
|
|
199
|
+
the body already uses (PR9 item #2 — typed construction surface).
|
|
200
|
+
"""
|
|
201
|
+
# --- unpack deps ---
|
|
202
|
+
llm = deps.llm
|
|
203
|
+
orchestrator = deps.orchestrator
|
|
204
|
+
page_extraction_llm = deps.page_extraction_llm
|
|
205
|
+
system_prompt_class = deps.system_prompt_class
|
|
206
|
+
register_new_step_callback = deps.register_new_step_callback
|
|
207
|
+
register_done_callback = deps.register_done_callback
|
|
208
|
+
injected_controller = deps.controller # UP-05: least-privilege child controller (or None)
|
|
209
|
+
# --- unpack config ---
|
|
210
|
+
task = config.task
|
|
211
|
+
use_vision = config.use_vision
|
|
212
|
+
save_conversation_path = config.save_conversation_path
|
|
213
|
+
save_conversation_path_encoding = config.save_conversation_path_encoding
|
|
214
|
+
max_failures = config.max_failures
|
|
215
|
+
retry_delay = config.retry_delay
|
|
216
|
+
max_input_tokens = config.max_input_tokens
|
|
217
|
+
validate_output = config.validate_output
|
|
218
|
+
# Operator gate: MemoryConfig.ENABLE_GIF_CREATION (env, default false) is the
|
|
219
|
+
# master switch. AgentConfig.generate_gif defaults True, so without this gate a
|
|
220
|
+
# headless server makes per-conversation GIFs nobody consumes (live-test waste).
|
|
221
|
+
generate_gif = _resolve_generate_gif(config.generate_gif, MemoryConfig.ENABLE_GIF_CREATION)
|
|
222
|
+
sensitive_data = config.sensitive_data
|
|
223
|
+
available_file_paths = config.available_file_paths
|
|
224
|
+
include_attributes = config.include_attributes
|
|
225
|
+
max_error_length = config.max_error_length
|
|
226
|
+
max_actions_per_step = config.max_actions_per_step
|
|
227
|
+
initial_actions = config.initial_actions
|
|
228
|
+
tool_calling_method = config.tool_calling_method
|
|
229
|
+
agent_name = config.agent_name
|
|
230
|
+
session_config = config.session_config
|
|
231
|
+
profile_id = config.profile_id
|
|
232
|
+
profile_overrides = config.profile_overrides
|
|
233
|
+
use_native_tools = config.use_native_tools
|
|
234
|
+
step_timeout_seconds = config.step_timeout_seconds
|
|
235
|
+
stall_timeout_seconds = config.stall_timeout_seconds
|
|
236
|
+
max_step_timeout = config.max_step_timeout
|
|
237
|
+
is_sub_agent = config.is_sub_agent
|
|
238
|
+
parent_session_id = config.parent_session_id
|
|
239
|
+
role = config.role
|
|
240
|
+
# INPUT VALIDATION: Validate critical configuration parameters
|
|
241
|
+
if not isinstance(max_failures, int) or max_failures < 0:
|
|
242
|
+
raise ROBValidationError(f"max_failures must be a non-negative integer, got {max_failures}")
|
|
243
|
+
|
|
244
|
+
if not isinstance(retry_delay, int) or retry_delay < 0:
|
|
245
|
+
raise ROBValidationError(f"retry_delay must be a non-negative integer, got {retry_delay}")
|
|
246
|
+
|
|
247
|
+
if max_input_tokens is not None and (not isinstance(max_input_tokens, int) or max_input_tokens <= 0):
|
|
248
|
+
raise ROBValidationError(f"max_input_tokens must be a positive integer or None, got {max_input_tokens}")
|
|
249
|
+
|
|
250
|
+
if not isinstance(max_actions_per_step, int) or max_actions_per_step <= 0:
|
|
251
|
+
raise ROBValidationError(f"max_actions_per_step must be a positive integer, got {max_actions_per_step}")
|
|
252
|
+
|
|
253
|
+
if not isinstance(max_error_length, int) or max_error_length <= 0:
|
|
254
|
+
raise ROBValidationError(f"max_error_length must be a positive integer, got {max_error_length}")
|
|
255
|
+
|
|
256
|
+
# Validate task is not empty
|
|
257
|
+
if not task or not isinstance(task, str):
|
|
258
|
+
raise ROBValidationError("task must be a non-empty string")
|
|
259
|
+
|
|
260
|
+
# Validate orchestrator is provided (single source of truth for session state)
|
|
261
|
+
if not orchestrator:
|
|
262
|
+
raise ROBValidationError("orchestrator is required - Agent cannot run standalone")
|
|
263
|
+
|
|
264
|
+
# Store orchestrator reference (single source of truth for session_id and user_id)
|
|
265
|
+
# DO NOT copy session_id or user_id - use properties instead
|
|
266
|
+
self.orchestrator = orchestrator
|
|
267
|
+
|
|
268
|
+
# Store the agent name
|
|
269
|
+
self.agent_name = agent_name
|
|
270
|
+
|
|
271
|
+
# Sub-agent isolation flags
|
|
272
|
+
# Sub-agents skip H-MEM and message persistence to prevent context pollution
|
|
273
|
+
self._is_sub_agent = is_sub_agent
|
|
274
|
+
self._parent_session_id = parent_session_id
|
|
275
|
+
# Delegation role: a "leaf" agent cannot call delegate_task (roadmap P1).
|
|
276
|
+
# Sub-agents are spawned as "leaf"; the main agent defaults to "orchestrator".
|
|
277
|
+
self._role = "leaf" if is_sub_agent else role
|
|
278
|
+
|
|
279
|
+
# Initialize Tool Call Tracker for robust ID management
|
|
280
|
+
# Note: Uses orchestrator.session_id directly
|
|
281
|
+
self.tool_call_tracker = ToolCallTracker(
|
|
282
|
+
session_id=self.orchestrator.session_id,
|
|
283
|
+
logger=None # Will use its own logger
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Stall detection interval (state.last_activity_time set later after state is created)
|
|
287
|
+
self._stall_check_interval = 60 # Check every minute
|
|
288
|
+
|
|
289
|
+
# Use orchestrator's infrastructure (single instances per session)
|
|
290
|
+
self.session_manager = self.orchestrator.session_manager
|
|
291
|
+
self.telemetry_manager = self.orchestrator.telemetry_manager
|
|
292
|
+
|
|
293
|
+
# Initialize logger early so it's available for profile and session config processing
|
|
294
|
+
from agents.task.logging_config import get_task_logger
|
|
295
|
+
self.logger = get_task_logger(self.agent_name, self.orchestrator.session_id)
|
|
296
|
+
|
|
297
|
+
# RESTORATION: Load saved tool call tracker state from previous session
|
|
298
|
+
# NOTE: Moved after logger initialization to fix unreachable code issue
|
|
299
|
+
if self.session_id and orchestrator:
|
|
300
|
+
try:
|
|
301
|
+
from agents.task.path import pm
|
|
302
|
+
tool_calls_file = pm().create_file_path(
|
|
303
|
+
session_id=self.session_id,
|
|
304
|
+
subdir_name="data",
|
|
305
|
+
filename="tool_calls.json",
|
|
306
|
+
user_id=orchestrator.user_id if hasattr(orchestrator, 'user_id') else None
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
if tool_calls_file.exists():
|
|
310
|
+
if self.tool_call_tracker.load_from_file(tool_calls_file):
|
|
311
|
+
self.logger.info("📂 Restored tool call tracker state from previous session")
|
|
312
|
+
except Exception as e:
|
|
313
|
+
self.logger.debug(f"No tool call tracker state to restore: {e}")
|
|
314
|
+
|
|
315
|
+
# Apply profile configuration if specified (after logger is initialized)
|
|
316
|
+
if profile_id:
|
|
317
|
+
from agents.task.agent.profile_manager import ProfileManager
|
|
318
|
+
|
|
319
|
+
# Load and apply profile configuration
|
|
320
|
+
updated_config = ProfileManager.load_and_apply(profile_id, locals(), profile_overrides)
|
|
321
|
+
|
|
322
|
+
# Apply system message if profile provided one
|
|
323
|
+
if '_profile_system_message' in updated_config:
|
|
324
|
+
self._profile_system_message = updated_config['_profile_system_message']
|
|
325
|
+
|
|
326
|
+
# Create LLM from profile config if provided and not already set
|
|
327
|
+
if '_profile_llm_config' in updated_config and not llm:
|
|
328
|
+
llm_config = updated_config['_profile_llm_config']
|
|
329
|
+
llm = self._create_llm_from_config(llm_config)
|
|
330
|
+
if llm:
|
|
331
|
+
self.logger.info(f"Created LLM from profile config: {llm_config.get('provider')}/{llm_config.get('model')}")
|
|
332
|
+
else:
|
|
333
|
+
self.logger.warning(f"Could not create LLM from config: {llm_config}")
|
|
334
|
+
|
|
335
|
+
# Apply other profile settings
|
|
336
|
+
if '_enabled_actions' in updated_config:
|
|
337
|
+
self._enabled_actions = updated_config['_enabled_actions']
|
|
338
|
+
if '_profile_max_steps' in updated_config:
|
|
339
|
+
self._profile_max_steps = updated_config['_profile_max_steps']
|
|
340
|
+
|
|
341
|
+
# Apply profile settings explicitly. The old `locals()[key] = ...` loop was
|
|
342
|
+
# a no-op (assigning to locals() does not rebind), silently dropping these
|
|
343
|
+
# overrides — see resolve_profile_overrides.
|
|
344
|
+
tool_calling_method, max_actions_per_step, max_input_tokens, max_failures = \
|
|
345
|
+
resolve_profile_overrides(
|
|
346
|
+
updated_config,
|
|
347
|
+
tool_calling_method=tool_calling_method,
|
|
348
|
+
max_actions_per_step=max_actions_per_step,
|
|
349
|
+
max_input_tokens=max_input_tokens,
|
|
350
|
+
max_failures=max_failures,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
# Initialize tool call adapter for normalizing tool calls across providers
|
|
354
|
+
# This is initialized later after controller is set up
|
|
355
|
+
|
|
356
|
+
# Process session config if provided
|
|
357
|
+
self.session_config = None
|
|
358
|
+
if session_config:
|
|
359
|
+
try:
|
|
360
|
+
from agents.task.config import TaskSessionConfig
|
|
361
|
+
|
|
362
|
+
# Create config from dict
|
|
363
|
+
self.session_config = TaskSessionConfig.from_dict(session_config)
|
|
364
|
+
|
|
365
|
+
# Apply config values (override individual parameters)
|
|
366
|
+
|
|
367
|
+
# Apply limits - override parameters if session config provides them
|
|
368
|
+
if self.session_config.limits.max_actions_per_step is not None:
|
|
369
|
+
max_actions_per_step = self.session_config.limits.max_actions_per_step
|
|
370
|
+
if self.session_config.limits.max_input_tokens is not None:
|
|
371
|
+
max_input_tokens = self.session_config.limits.max_input_tokens
|
|
372
|
+
if self.session_config.limits.max_failures is not None:
|
|
373
|
+
max_failures = self.session_config.limits.max_failures
|
|
374
|
+
|
|
375
|
+
# CRITICAL FIX: Apply LLM config from session_config
|
|
376
|
+
# Session config should OVERRIDE the profile's LLM (session is more specific)
|
|
377
|
+
# This was previously missing, causing user's model selection to be ignored!
|
|
378
|
+
if self.session_config.llm:
|
|
379
|
+
# Check if session config specifies a non-default model
|
|
380
|
+
# Default is gpt-5/openai - if user specified something different, use it
|
|
381
|
+
is_non_default_model = (
|
|
382
|
+
self.session_config.llm.model != "gpt-5" or
|
|
383
|
+
self.session_config.llm.provider != "openai"
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
if is_non_default_model or not llm:
|
|
387
|
+
llm_config = {
|
|
388
|
+
'model': self.session_config.llm.model,
|
|
389
|
+
'provider': self.session_config.llm.provider,
|
|
390
|
+
'temperature': self.session_config.llm.temperature,
|
|
391
|
+
'use_vision': self.session_config.llm.use_vision
|
|
392
|
+
}
|
|
393
|
+
self.logger.info(f"Creating LLM from session config: {llm_config['provider']}/{llm_config['model']}")
|
|
394
|
+
new_llm = self._create_llm_from_config(llm_config)
|
|
395
|
+
if new_llm:
|
|
396
|
+
llm = new_llm # Override the profile LLM
|
|
397
|
+
self.logger.info(f"✅ Created LLM from session config: {llm_config['provider']}/{llm_config['model']}")
|
|
398
|
+
else:
|
|
399
|
+
self.logger.error(f"❌ Failed to create LLM from session config: {llm_config}, keeping existing LLM")
|
|
400
|
+
|
|
401
|
+
# Save config to session
|
|
402
|
+
self._save_session_config()
|
|
403
|
+
|
|
404
|
+
self.logger.info("Applied session config")
|
|
405
|
+
|
|
406
|
+
except Exception as e:
|
|
407
|
+
self.logger.warning(f"Failed to apply session config: {e}")
|
|
408
|
+
# Continue with individual parameters as fallback
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
# HRE removed - was experimental feature
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
# Optional memory profiling setup behind environment flag
|
|
415
|
+
if os.getenv("PROFILE_MEM") == "1":
|
|
416
|
+
try:
|
|
417
|
+
import tracemalloc
|
|
418
|
+
import atexit
|
|
419
|
+
import pprint
|
|
420
|
+
|
|
421
|
+
tracemalloc.start()
|
|
422
|
+
self.logger.info("Memory profiling enabled - tracemalloc started")
|
|
423
|
+
|
|
424
|
+
@atexit.register
|
|
425
|
+
def _dump_memory_stats():
|
|
426
|
+
try:
|
|
427
|
+
top = tracemalloc.take_snapshot().statistics("lineno")[:20]
|
|
428
|
+
self.logger.info("Top 20 memory allocations:")
|
|
429
|
+
pprint.pprint(top)
|
|
430
|
+
except Exception as e:
|
|
431
|
+
self.logger.warning(f"Error dumping memory stats: {e}")
|
|
432
|
+
except ImportError:
|
|
433
|
+
self.logger.warning("tracemalloc not available for memory profiling")
|
|
434
|
+
except Exception as e:
|
|
435
|
+
self.logger.warning(f"Failed to set up memory profiling: {e}")
|
|
436
|
+
|
|
437
|
+
# Logger already initialized earlier, just log that we're ready
|
|
438
|
+
# (This used to be where logger was initialized, but we moved it earlier
|
|
439
|
+
# to be available for session_config processing)
|
|
440
|
+
|
|
441
|
+
# Set the agent_id - combine agent_name and clean session_id
|
|
442
|
+
# Format: agent_name_session_id (e.g., agent_1234-5678)
|
|
443
|
+
self.agent_id = f"{self.agent_name}_{self.orchestrator.session_id}"
|
|
444
|
+
|
|
445
|
+
# Debug log to track session ID usage
|
|
446
|
+
self.logger.debug(f"Agent initialized with ID: {self.agent_id}")
|
|
447
|
+
|
|
448
|
+
# Initialize HITL Manager (after agent_id is set)
|
|
449
|
+
from agents.task.agent.hitl_manager import HITLManager
|
|
450
|
+
self.hitl_manager = HITLManager(
|
|
451
|
+
session_id=self.orchestrator.session_id,
|
|
452
|
+
agent_id=self.agent_id,
|
|
453
|
+
logger=self.logger,
|
|
454
|
+
telemetry_manager=self.telemetry_manager
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
# NOTE: model_name will be available via property after MessageManager is created
|
|
458
|
+
# DO NOT extract it here - MessageManager is the single source of truth
|
|
459
|
+
|
|
460
|
+
# NOTE: Agent registration is handled by SessionOrchestrator.create_agent()
|
|
461
|
+
# Agent only uses SessionManager for metadata updates via _update_session_metadata()
|
|
462
|
+
# This prevents duplicate registration and ensures single source of truth
|
|
463
|
+
|
|
464
|
+
# Basic configuration
|
|
465
|
+
self.sensitive_data = sensitive_data
|
|
466
|
+
self.task = task
|
|
467
|
+
self.use_vision = use_vision
|
|
468
|
+
self.llm = llm
|
|
469
|
+
|
|
470
|
+
# Set chat_model_library for provider detection (needed by set_tool_calling_method)
|
|
471
|
+
# This must be set BEFORE calling set_tool_calling_method() at line 427
|
|
472
|
+
self.chat_model_library = self.llm.__class__.__name__
|
|
473
|
+
|
|
474
|
+
# Store agent type for telemetry
|
|
475
|
+
self.agent_type = self.__class__.__name__
|
|
476
|
+
|
|
477
|
+
self.use_native_tools = use_native_tools
|
|
478
|
+
# Store timeout configuration with validation
|
|
479
|
+
self.step_timeout_seconds = step_timeout_seconds or 600
|
|
480
|
+
self.stall_timeout_seconds = stall_timeout_seconds or 600
|
|
481
|
+
self.max_step_timeout = max_step_timeout
|
|
482
|
+
|
|
483
|
+
# Ensure step timeout doesn't exceed max
|
|
484
|
+
if self.step_timeout_seconds > self.max_step_timeout:
|
|
485
|
+
self.logger.warning(f"step_timeout_seconds {self.step_timeout_seconds} exceeds max {self.max_step_timeout}, using max")
|
|
486
|
+
self.step_timeout_seconds = self.max_step_timeout
|
|
487
|
+
|
|
488
|
+
# NOTE: Approval state now managed by HITLManager
|
|
489
|
+
|
|
490
|
+
# Page extraction LLM setup
|
|
491
|
+
if not page_extraction_llm:
|
|
492
|
+
self.page_extraction_llm = llm
|
|
493
|
+
else:
|
|
494
|
+
self.page_extraction_llm = page_extraction_llm
|
|
495
|
+
|
|
496
|
+
# File paths
|
|
497
|
+
self.available_file_paths = available_file_paths
|
|
498
|
+
|
|
499
|
+
# Normalize save_conversation_path if provided
|
|
500
|
+
if save_conversation_path:
|
|
501
|
+
# Import centralized path manager
|
|
502
|
+
from agents.task.path import pm
|
|
503
|
+
|
|
504
|
+
# Use centralized path manager to create proper file path
|
|
505
|
+
self.save_conversation_path = str(pm().create_file_path(
|
|
506
|
+
self.orchestrator.session_id,
|
|
507
|
+
"logs",
|
|
508
|
+
os.path.basename(save_conversation_path) or "conversation",
|
|
509
|
+
user_id=self.orchestrator.user_id # Pass user_id to PM
|
|
510
|
+
))
|
|
511
|
+
self.logger.info(f"Using normalized conversation path: {self.save_conversation_path}")
|
|
512
|
+
else:
|
|
513
|
+
self.save_conversation_path = None
|
|
514
|
+
|
|
515
|
+
self.save_conversation_path_encoding = save_conversation_path_encoding
|
|
516
|
+
|
|
517
|
+
# State tracking
|
|
518
|
+
self._last_result = None
|
|
519
|
+
self._cancelled = False # Cancellation flag for stopping execution
|
|
520
|
+
self._deferred_mcp_actions: List[Dict[str, Any]] = [] # MCP actions deferred from previous step
|
|
521
|
+
self._max_mcp_deferrals = 2 # Cap deferrals to prevent infinite loops
|
|
522
|
+
# Set default for include_attributes if not provided
|
|
523
|
+
if include_attributes is None:
|
|
524
|
+
include_attributes = [
|
|
525
|
+
'title',
|
|
526
|
+
'type',
|
|
527
|
+
'name',
|
|
528
|
+
'role',
|
|
529
|
+
'tabindex',
|
|
530
|
+
'aria-label',
|
|
531
|
+
'placeholder',
|
|
532
|
+
'value',
|
|
533
|
+
'alt',
|
|
534
|
+
'aria-expanded',
|
|
535
|
+
]
|
|
536
|
+
self.include_attributes = include_attributes
|
|
537
|
+
self.max_error_length = max_error_length
|
|
538
|
+
self.max_input_tokens = max_input_tokens # Store max_input_tokens for MessageManager
|
|
539
|
+
self.generate_gif = generate_gif
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
# Controller: an injected (least-privilege child) controller takes precedence
|
|
544
|
+
# (UP-05); otherwise the shared orchestrator controller. All downstream
|
|
545
|
+
# derivations (ActionModel, action_descriptions, mcp_servers_info,
|
|
546
|
+
# message_manager) follow from self.controller, so this is the only seam.
|
|
547
|
+
self.controller = injected_controller or self.orchestrator.controller
|
|
548
|
+
_ctl_src = "injected (least-privilege)" if injected_controller else "orchestrator"
|
|
549
|
+
self.logger.debug(f"Using {_ctl_src} controller with {len(self.controller.list_tools())} tools")
|
|
550
|
+
|
|
551
|
+
self.max_actions_per_step = max_actions_per_step
|
|
552
|
+
|
|
553
|
+
# WS-A capability gate: while the session is tainted by untrusted correspondent
|
|
554
|
+
# DATA, deny high-impact tools (money/comms/code-exec/delegation). Registered
|
|
555
|
+
# fail-CLOSED, gated on the access model + a real orchestrator taint flag. The
|
|
556
|
+
# owner clears the taint by sending a genuine turn. No-op when the flag is off.
|
|
557
|
+
try:
|
|
558
|
+
from agents.task.surface_config import SurfaceConfig
|
|
559
|
+
if SurfaceConfig.correspondent_access_enabled() and self.controller is not None \
|
|
560
|
+
and hasattr(self.controller, "register_pre_tool_call_hook"):
|
|
561
|
+
from agents.task.agent.core.correspondent_gate import (
|
|
562
|
+
build_tool_resolver, make_correspondent_gate_hook)
|
|
563
|
+
_orch = self.orchestrator
|
|
564
|
+
# Resolve each action's owning tool_id so the tool-id-level denylist
|
|
565
|
+
# actually fires — the pre-hook only ever sees the bare action name
|
|
566
|
+
# (run_code, goal_create, x402_fetch, dynamic MCP {server}_{tool}).
|
|
567
|
+
_gate = make_correspondent_gate_hook(
|
|
568
|
+
lambda: bool(getattr(_orch, "_correspondent_tainted", False)),
|
|
569
|
+
resolve_tool=build_tool_resolver(self.controller))
|
|
570
|
+
self.controller.register_pre_tool_call_hook(_gate, fail_mode="closed")
|
|
571
|
+
except Exception as e:
|
|
572
|
+
self.logger.debug(f"correspondent capability gate not registered: {e}")
|
|
573
|
+
|
|
574
|
+
# ToolCallAdapter removed - functionality integrated into MessageManager and Registry
|
|
575
|
+
# (tool calls now flow through ToolCallBuilder + Registry).
|
|
576
|
+
|
|
577
|
+
# Browser context obtained on-demand via property (see get_browser_context())
|
|
578
|
+
|
|
579
|
+
self.system_prompt_class = system_prompt_class
|
|
580
|
+
|
|
581
|
+
# Agent gets pre-configured ActionModel from Controller
|
|
582
|
+
self.ActionModel = self.controller.create_action_model()
|
|
583
|
+
self.AgentOutput = AgentOutput.type_with_custom_actions(self.ActionModel)
|
|
584
|
+
|
|
585
|
+
self._set_version_and_source()
|
|
586
|
+
|
|
587
|
+
# Token limits are now calculated by MessageManager (SINGLE SOURCE OF TRUTH)
|
|
588
|
+
# Just pass max_input_tokens override if provided, otherwise MessageManager auto-calculates
|
|
589
|
+
|
|
590
|
+
self.tool_calling_method = self.set_tool_calling_method(tool_calling_method)
|
|
591
|
+
|
|
592
|
+
# NOTE: Provider detection will happen after MessageManager is created
|
|
593
|
+
# For now, detect provider just to check native tools support
|
|
594
|
+
# This is a temporary detection - model_name property will delegate to MessageManager
|
|
595
|
+
_temp_model_name = self._extract_model_name(llm)
|
|
596
|
+
provider = detect_llm_provider(None, _temp_model_name)
|
|
597
|
+
|
|
598
|
+
# Preserve user intent - intersect user preference with provider capability.
|
|
599
|
+
# This also re-assigns self.use_native_tools so the agent's own flag matches
|
|
600
|
+
# what MessageManager is given (see _reconcile_native_tools).
|
|
601
|
+
use_native_tools = self._reconcile_native_tools(provider)
|
|
602
|
+
|
|
603
|
+
# Cache action descriptions for reuse (single call to controller).
|
|
604
|
+
# T1-03: in native mode the full parameter schemas already ship to the
|
|
605
|
+
# provider in the `tools` param, so the prompt gets a compact one-line
|
|
606
|
+
# index instead of the raw schema dump (2.5-6k redundant tokens/session).
|
|
607
|
+
# The JSON-fallback path keeps the full dump — there it IS the schema.
|
|
608
|
+
if use_native_tools:
|
|
609
|
+
self.action_descriptions = self.controller.get_prompt_action_index()
|
|
610
|
+
else:
|
|
611
|
+
self.action_descriptions = self.controller.get_prompt_description()
|
|
612
|
+
|
|
613
|
+
# Get MCP server info for dynamic prompt generation (no hardcoded examples)
|
|
614
|
+
self.mcp_servers_info = self.controller.get_mcp_servers_info()
|
|
615
|
+
|
|
616
|
+
# Hierarchical Memory System - MANDATORY for main agents, SKIPPED for sub-agents
|
|
617
|
+
# Sub-agents don't need H-MEM - they do focused tasks and return results
|
|
618
|
+
self.task_context_manager = None
|
|
619
|
+
self._created_task_context_manager = False
|
|
620
|
+
|
|
621
|
+
if self._is_sub_agent:
|
|
622
|
+
# SUB-AGENT ISOLATION: Use NullTaskContextManager to prevent context pollution
|
|
623
|
+
# FIX (Jan 2026): Use NullObject pattern instead of None to prevent AttributeError
|
|
624
|
+
# Sub-agents extract their own output which is returned to parent
|
|
625
|
+
from modules.memory.task.null_context_manager import NullTaskContextManager
|
|
626
|
+
self.task_context_manager = NullTaskContextManager()
|
|
627
|
+
self.logger.info(f"🔒 Sub-agent mode: Using NullTaskContextManager (parent: {self._parent_session_id[:20] if self._parent_session_id else 'unknown'}...)")
|
|
628
|
+
else:
|
|
629
|
+
# Main agent: Initialize H-MEM as usual
|
|
630
|
+
# Try to get from container first
|
|
631
|
+
try:
|
|
632
|
+
memory_manager = self.orchestrator.container.get_service('memory_manager')
|
|
633
|
+
if memory_manager and hasattr(memory_manager, 'task_context_manager'):
|
|
634
|
+
self.task_context_manager = memory_manager.task_context_manager
|
|
635
|
+
self.logger.info("✅ Using TaskContextManager from container")
|
|
636
|
+
except Exception as e:
|
|
637
|
+
self.logger.debug(f"Could not get TaskContextManager from container: {e}")
|
|
638
|
+
|
|
639
|
+
# If not available, create dedicated instance for this session
|
|
640
|
+
if not self.task_context_manager:
|
|
641
|
+
self.logger.warning(
|
|
642
|
+
"No memory_manager in container — creating a dedicated "
|
|
643
|
+
"TaskContextManager for this session (cross-agent memory will NOT "
|
|
644
|
+
"be shared)"
|
|
645
|
+
)
|
|
646
|
+
from modules.memory.task.task_context_manager import TaskContextManager
|
|
647
|
+
from core.config import BotConfig
|
|
648
|
+
|
|
649
|
+
# Create instance
|
|
650
|
+
config = self.orchestrator.container.config if self.orchestrator.container else BotConfig()
|
|
651
|
+
self.task_context_manager = TaskContextManager(
|
|
652
|
+
name=f"task_context_{self.session_id}",
|
|
653
|
+
config=config
|
|
654
|
+
)
|
|
655
|
+
self._created_task_context_manager = True
|
|
656
|
+
|
|
657
|
+
# Opt-in external cross-session memory backend (MEMORY_BACKEND=sqlite|local_vector).
|
|
658
|
+
# Registered once via the existing one-external-provider registry; the live
|
|
659
|
+
# prefetch/sync_turn wiring (step.py + _save_step_to_memory) already routes through
|
|
660
|
+
# registry.active(). For local_vector we hand it the container's already-loaded local
|
|
661
|
+
# embedding model (no second copy); it degrades to FTS5-only if absent.
|
|
662
|
+
# NOTE: data_dir lives on the container's BotConfig — Agent has no self.config.
|
|
663
|
+
try:
|
|
664
|
+
from modules.memory.backend_factory import maybe_register_memory_backend
|
|
665
|
+
_container = getattr(getattr(self, "orchestrator", None), "container", None)
|
|
666
|
+
_cfg = getattr(_container, "config", None)
|
|
667
|
+
data_dir = getattr(_cfg, "data_dir", "data") or "data"
|
|
668
|
+
_embedder = None
|
|
669
|
+
if _container is not None and getattr(_container, "has_service", None):
|
|
670
|
+
try:
|
|
671
|
+
if _container.has_service("embedding_model"):
|
|
672
|
+
_embedder = _container.get_service("embedding_model")
|
|
673
|
+
except Exception:
|
|
674
|
+
_embedder = None
|
|
675
|
+
_provider = maybe_register_memory_backend(data_dir=data_dir, embedding_model=_embedder)
|
|
676
|
+
# The Controller's session_search/memory_search registration gate ran at
|
|
677
|
+
# Controller.__init__ — BEFORE this backend registration — so on the FIRST
|
|
678
|
+
# session of a process (e.g. `polyrob run`) it saw no external provider and
|
|
679
|
+
# skipped the recall tools (W6). Re-run the gated registration now that the
|
|
680
|
+
# backend exists. Idempotent: guarded on not-already-registered so a later
|
|
681
|
+
# session (backend already up at Controller init) doesn't double-register.
|
|
682
|
+
if (_provider is not None and getattr(_provider, "is_external", False)
|
|
683
|
+
and self.controller is not None):
|
|
684
|
+
try:
|
|
685
|
+
_names = self.controller.registry.list_action_names()
|
|
686
|
+
if "session_search" not in _names:
|
|
687
|
+
self.controller._register_session_search_action()
|
|
688
|
+
# ME-D1: same first-session gap for the other provider-gated tools —
|
|
689
|
+
# their gates ran at Controller init, before the backend existed.
|
|
690
|
+
if "recent_activity" not in _names:
|
|
691
|
+
self.controller._register_recent_activity_action()
|
|
692
|
+
if "memory" not in _names:
|
|
693
|
+
self.controller._register_memory_tool_action()
|
|
694
|
+
except Exception as e:
|
|
695
|
+
self.logger.debug(f"memory tool re-registration skipped: {e}")
|
|
696
|
+
except Exception as e:
|
|
697
|
+
self.logger.debug(f"memory backend registration skipped: {e}")
|
|
698
|
+
|
|
699
|
+
# P2-24: recompute the prompt action index AFTER the memory-backend
|
|
700
|
+
# registration above (registers session_search/recent_activity/memory on the
|
|
701
|
+
# FIRST session of a process). It was computed earlier, before those tools
|
|
702
|
+
# existed, so the first session's system prompt omitted them while the per-
|
|
703
|
+
# step schemas included them. action_descriptions is not consumed until the
|
|
704
|
+
# MessageManager build below, so recomputing here is safe. Native only.
|
|
705
|
+
try:
|
|
706
|
+
if use_native_tools and self.controller is not None:
|
|
707
|
+
self.action_descriptions = self.controller.get_prompt_action_index()
|
|
708
|
+
except Exception as _aidx_err:
|
|
709
|
+
self.logger.debug(f"action-index recompute skipped: {_aidx_err}")
|
|
710
|
+
|
|
711
|
+
# Load skills for this session and embed into system message
|
|
712
|
+
# Skills are per-session prompt extensions that provide context-aware guidance
|
|
713
|
+
system_message = getattr(self, '_profile_system_message', None)
|
|
714
|
+
skill_content = None
|
|
715
|
+
|
|
716
|
+
try:
|
|
717
|
+
from agents.task.agent.skill_manager import get_skill_manager
|
|
718
|
+
skill_manager = get_skill_manager()
|
|
719
|
+
|
|
720
|
+
# Get tool_ids from controller (what tools are loaded for this session)
|
|
721
|
+
tool_ids = self.controller.list_tools() if self.controller else []
|
|
722
|
+
|
|
723
|
+
# Get available actions for more precise matching
|
|
724
|
+
available_actions = []
|
|
725
|
+
if self.controller and hasattr(self.controller, 'registry'):
|
|
726
|
+
available_actions = self.controller.registry.list_action_names()
|
|
727
|
+
|
|
728
|
+
# Match skills based on session context (include user's custom skills)
|
|
729
|
+
user_id = self.orchestrator.user_id if hasattr(self.orchestrator, 'user_id') else None
|
|
730
|
+
from agents.task.templates import seeded_skills_for
|
|
731
|
+
_seeded = seeded_skills_for(os.environ.get("POLYROB_PERSONA"))
|
|
732
|
+
matched_skills = skill_manager.get_skills_for_session(
|
|
733
|
+
tool_ids=tool_ids,
|
|
734
|
+
task=self.task,
|
|
735
|
+
available_actions=available_actions,
|
|
736
|
+
user_id=user_id,
|
|
737
|
+
seeded_skill_ids=_seeded or None,
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
# S9 fix (default OFF -> byte-identical): with progressive disclosure, optionally
|
|
741
|
+
# expose ALL available skills in the catalog so the agent can discover + load_skill
|
|
742
|
+
# any of them (not just trigger-matched ones). Matched skills stay first (priority).
|
|
743
|
+
from agents.task.constants import (
|
|
744
|
+
skill_progressive_disclosure as _prog_disc,
|
|
745
|
+
skill_catalog_include_all as _catalog_include_all,
|
|
746
|
+
)
|
|
747
|
+
# Access-time read (not the import-bound constant) so the local-mode
|
|
748
|
+
# default (ON under POLYROB_LOCAL) is honored for the terminal agent.
|
|
749
|
+
if _prog_disc() and _catalog_include_all():
|
|
750
|
+
try:
|
|
751
|
+
_seen = {s.skill_id for s in matched_skills}
|
|
752
|
+
# SK-F1: the builtin library is 23 rules and every authored skill
|
|
753
|
+
# defaults to priority 6 (the old cap-20 cut band) — 50 is comfortably
|
|
754
|
+
# above both so the catalog actually reflects the full library.
|
|
755
|
+
_extra = [s for s in skill_manager.get_catalog_skills(user_id=user_id, max_skills=50, tool_ids=tool_ids)
|
|
756
|
+
if s.skill_id not in _seen]
|
|
757
|
+
matched_skills = list(matched_skills) + _extra
|
|
758
|
+
except Exception as _cat_err:
|
|
759
|
+
self.logger.debug(f"catalog-include-all skipped (non-fatal): {_cat_err}")
|
|
760
|
+
|
|
761
|
+
if matched_skills:
|
|
762
|
+
# S-1: progressive disclosure. When enabled, inject only a compact
|
|
763
|
+
# <skill-catalog> and let the agent pull full bodies via load_skill;
|
|
764
|
+
# otherwise (default) embed full bodies as today. Either way the
|
|
765
|
+
# matched skills (with pre-loaded bodies) are handed to the controller
|
|
766
|
+
# so load_skill can serve them without re-reading disk.
|
|
767
|
+
from agents.task.constants import skill_progressive_disclosure
|
|
768
|
+
if skill_progressive_disclosure():
|
|
769
|
+
skill_content = skill_manager.format_skill_catalog(matched_skills)
|
|
770
|
+
if self.controller is not None:
|
|
771
|
+
self.controller._session_skills = {s.skill_id: s for s in matched_skills}
|
|
772
|
+
self.logger.info(
|
|
773
|
+
f"✨ Skill catalog ({len(matched_skills)}) for session "
|
|
774
|
+
f"[progressive disclosure]: {[s.skill_id for s in matched_skills]}"
|
|
775
|
+
)
|
|
776
|
+
else:
|
|
777
|
+
skill_content = skill_manager.format_skills_for_prompt(matched_skills)
|
|
778
|
+
self.logger.info(f"✨ Loaded {len(matched_skills)} skills for session: {[s.skill_id for s in matched_skills]}")
|
|
779
|
+
# SK-F3: with progressive disclosure OFF, skills are eager-injected
|
|
780
|
+
# full-body (no load_skill call), so usage was never recorded and the
|
|
781
|
+
# curator archived authored skills as "never used." Bump provenance
|
|
782
|
+
# here too, at the only choke point this path has.
|
|
783
|
+
_bump_eager_skill_usage(matched_skills, user_id, self.logger)
|
|
784
|
+
|
|
785
|
+
# Save skills info to session directory for webview
|
|
786
|
+
try:
|
|
787
|
+
from agents.task.path import pm
|
|
788
|
+
import json
|
|
789
|
+
# P2-25: precompute user-authored skill ids via the REAL method
|
|
790
|
+
# (_load_user_rules returns (rules, dir); the old hasattr referenced
|
|
791
|
+
# a nonexistent _load_user_skill_rules, so is_user_skill was always
|
|
792
|
+
# the startswith/endswith heuristic).
|
|
793
|
+
_user_skill_ids = set()
|
|
794
|
+
try:
|
|
795
|
+
if user_id and hasattr(skill_manager, '_load_user_rules'):
|
|
796
|
+
_ur, _ = skill_manager._load_user_rules(user_id)
|
|
797
|
+
_user_skill_ids = set((_ur or {}).keys())
|
|
798
|
+
except Exception:
|
|
799
|
+
_user_skill_ids = set()
|
|
800
|
+
skills_data = [
|
|
801
|
+
{
|
|
802
|
+
"id": s.skill_id,
|
|
803
|
+
"name": s.skill_id.replace("-", " ").title(),
|
|
804
|
+
"priority": s.priority,
|
|
805
|
+
"trigger_type": s.trigger_type,
|
|
806
|
+
"is_user_skill": s.skill_id.startswith("forked-") or s.skill_id.endswith("-custom") or s.skill_id in _user_skill_ids
|
|
807
|
+
}
|
|
808
|
+
for s in matched_skills
|
|
809
|
+
]
|
|
810
|
+
skills_path = pm().create_file_path(
|
|
811
|
+
session_id=self.orchestrator.session_id,
|
|
812
|
+
subdir_name="",
|
|
813
|
+
filename="skills.json",
|
|
814
|
+
user_id=user_id
|
|
815
|
+
)
|
|
816
|
+
with open(skills_path, 'w') as f:
|
|
817
|
+
json.dump(skills_data, f, indent=2)
|
|
818
|
+
self.logger.debug(f"Saved skills info to {skills_path}")
|
|
819
|
+
except Exception as save_err:
|
|
820
|
+
self.logger.debug(f"Could not save skills info (non-fatal): {save_err}")
|
|
821
|
+
except Exception as e:
|
|
822
|
+
self.logger.debug(f"Could not load skills (non-fatal): {e}")
|
|
823
|
+
|
|
824
|
+
# PR13: never embed skills into the system message - keep it stable/cacheable.
|
|
825
|
+
# Skills are injected as a pinned user message via set_skill_message() below.
|
|
826
|
+
# Here we just strip the placeholder from any profile system message.
|
|
827
|
+
if system_message:
|
|
828
|
+
content = system_message.content.replace("{SKILLS_PLACEHOLDER}", "")
|
|
829
|
+
system_message = SystemMessage(content=content)
|
|
830
|
+
|
|
831
|
+
# Store skill content for embedding later if SystemPrompt generates the message
|
|
832
|
+
self._skill_content = skill_content
|
|
833
|
+
|
|
834
|
+
# Initialize message manager with proper session ID and optional profile system message
|
|
835
|
+
# MessageManager becomes the SINGLE SOURCE OF TRUTH for model_name and provider_name
|
|
836
|
+
self.message_manager = MessageManager(
|
|
837
|
+
llm=self.llm,
|
|
838
|
+
task=self.task,
|
|
839
|
+
# Always provide action descriptions for context, even with native tools
|
|
840
|
+
# This helps the LLM understand what actions are available
|
|
841
|
+
action_descriptions=self.action_descriptions,
|
|
842
|
+
system_prompt_class=self.system_prompt_class,
|
|
843
|
+
max_input_tokens=self.max_input_tokens,
|
|
844
|
+
include_attributes=self.include_attributes,
|
|
845
|
+
max_error_length=self.max_error_length,
|
|
846
|
+
max_actions_per_step=self.max_actions_per_step,
|
|
847
|
+
sensitive_data=self.sensitive_data,
|
|
848
|
+
session_id=self.orchestrator.session_id, # Pass clean session_id from orchestrator
|
|
849
|
+
system_message=system_message, # Pass profile system message with skills embedded
|
|
850
|
+
use_native_tools=use_native_tools, # Pass native tools flag
|
|
851
|
+
tool_call_tracker=self.tool_call_tracker, # Pass tool call tracker for ID management
|
|
852
|
+
task_context_manager=self.task_context_manager, # Pass hierarchical memory system
|
|
853
|
+
mcp_servers=self.mcp_servers_info, # Pass MCP server info for dynamic prompts
|
|
854
|
+
persona_block=getattr(config, 'persona_block', None), # S1: chat-mode persona
|
|
855
|
+
# Session's loaded tools → config-aware prompt gating (e.g. <anysite> and
|
|
856
|
+
# <browser-tools> only render when the tool is actually loaded this
|
|
857
|
+
# session, not on a global flag).
|
|
858
|
+
tool_ids=self.controller.list_tools() if self.controller else [],
|
|
859
|
+
# T1-06: the vision prompt section follows the session's real use_vision
|
|
860
|
+
# instead of claiming image abilities for use_vision=False sessions.
|
|
861
|
+
include_vision=bool(self.use_vision),
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
# Agent-level provider label SSOT. The step-loop billing path reads
|
|
865
|
+
# self.llm_provider (next_action_internal.py) and the B3 per-request swap
|
|
866
|
+
# guard reads it for idempotence; without this it stayed unset on a
|
|
867
|
+
# never-swapped agent (billing recorded 'unknown', and swap_model was the
|
|
868
|
+
# only writer). Seed it from the MessageManager's detected provider so it
|
|
869
|
+
# is consistent with the SSOT the model_name/provider_name properties mirror.
|
|
870
|
+
self.llm_provider = self.message_manager.provider_name
|
|
871
|
+
|
|
872
|
+
# PR13: strip any skills placeholder from the generated system message; skills
|
|
873
|
+
# are NOT embedded in the system prompt anymore (kept stable for prompt caching).
|
|
874
|
+
if not getattr(self, '_profile_system_message', None):
|
|
875
|
+
current_content = self.message_manager._system_message.content
|
|
876
|
+
if "{SKILLS_PLACEHOLDER}" in current_content:
|
|
877
|
+
new_content = current_content.replace("{SKILLS_PLACEHOLDER}", "")
|
|
878
|
+
self.message_manager._system_message = SystemMessage(content=new_content)
|
|
879
|
+
self.message_manager._system_message_tokens = self.message_manager._count_message_tokens(
|
|
880
|
+
self.message_manager._system_message
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
# PR13: pin skills as a foundation user message (origin=SKILL), not the system
|
|
884
|
+
# prompt. get_messages_for_llm() injects this; the system prompt stays stable.
|
|
885
|
+
self.message_manager.set_skill_message(skill_content)
|
|
886
|
+
|
|
887
|
+
# polyrob Phase C: pin operator-authored SOUL/IDENTITY self-context as a frozen
|
|
888
|
+
# foundation message, read ONCE at session start from <data_dir>/identity/. This
|
|
889
|
+
# is operator-write-only (the agent never authors it in this cut). Empty/absent
|
|
890
|
+
# docs => set_self_context_message(None) => inert / byte-identical. Like skills,
|
|
891
|
+
# it lives in the foundation, NOT the system prompt, so the prompt stays cacheable.
|
|
892
|
+
try:
|
|
893
|
+
from core.instance import (load_self_context, load_self_doc,
|
|
894
|
+
load_owner_doc, resolve_instance_id)
|
|
895
|
+
_container = getattr(getattr(self, "orchestrator", None), "container", None)
|
|
896
|
+
_cfg = getattr(_container, "config", None)
|
|
897
|
+
_data_dir = getattr(_cfg, "data_dir", "data") or "data"
|
|
898
|
+
# SOUL tier (operator-only, instance-global) + the evolving SELF tier
|
|
899
|
+
# (agent-writable, per-(instance,user)). Both frozen at session start.
|
|
900
|
+
# load_self_doc applies the load-side [BLOCKED] guard; empty => omitted.
|
|
901
|
+
_soul = load_self_context(_data_dir)
|
|
902
|
+
_uid = self.orchestrator.user_id if hasattr(self.orchestrator, "user_id") else None
|
|
903
|
+
_self_doc = load_self_doc(_data_dir, _uid, resolve_instance_id())
|
|
904
|
+
# Bounded owner-facts doc (agent-maintained, per-(instance,user)): durable
|
|
905
|
+
# facts/preferences about the OWNER, injected after SOUL and before the
|
|
906
|
+
# evolving SELF doc. Load-side [BLOCKED] guard applies; empty => omitted.
|
|
907
|
+
_owner_doc = load_owner_doc(_data_dir, _uid, resolve_instance_id())
|
|
908
|
+
if _owner_doc:
|
|
909
|
+
_owner_doc = "## Owner facts\n\n" + _owner_doc
|
|
910
|
+
# WS-A + T1-13: the owner clause ("You act on behalf of OWNER X") renders
|
|
911
|
+
# whenever a DISTINCT owner principal resolves; the correspondent-DATA frame
|
|
912
|
+
# sentence stays gated on the three-tier access model being on. With no
|
|
913
|
+
# distinct owner and the model off this is "" — byte-identical legacy.
|
|
914
|
+
_awareness = ""
|
|
915
|
+
try:
|
|
916
|
+
from core.instance import owner_awareness_line
|
|
917
|
+
_corr_on = False
|
|
918
|
+
try:
|
|
919
|
+
from agents.task.surface_config import SurfaceConfig
|
|
920
|
+
_corr_on = SurfaceConfig.correspondent_access_enabled()
|
|
921
|
+
except Exception:
|
|
922
|
+
_corr_on = False
|
|
923
|
+
_awareness = owner_awareness_line(include_correspondent_frame=_corr_on)
|
|
924
|
+
except Exception:
|
|
925
|
+
_awareness = ""
|
|
926
|
+
_combined = "\n\n".join(p for p in (_awareness, _soul, _owner_doc, _self_doc) if p)
|
|
927
|
+
self.message_manager.set_self_context_message(_combined)
|
|
928
|
+
except Exception as e:
|
|
929
|
+
self.logger.debug(f"Could not load self-context (non-fatal): {e}")
|
|
930
|
+
|
|
931
|
+
# C9 + Phase 2: auto-load project context (polyrob.md/AGENTS.md/CLAUDE.md/
|
|
932
|
+
# .cursorrules) as a frozen PROJECT_CONTEXT foundation message.
|
|
933
|
+
# - Local CLI single-owner: loaded TRUSTED (read as steering), gated on
|
|
934
|
+
# project_context_autoload() (default ON under POLYROB_LOCAL).
|
|
935
|
+
# - Server: loaded ONLY when project_context_server_mode() is opted in, and
|
|
936
|
+
# then injected UNTRUSTED-WRAPPED (framed as DATA, not instructions) since
|
|
937
|
+
# the file may come from a repo the operator merely opened.
|
|
938
|
+
# Server stays byte-identical by default (both gates OFF). Fully fail-open —
|
|
939
|
+
# any error keeps _project_context_message=None.
|
|
940
|
+
try:
|
|
941
|
+
from agents.task.constants import AutonomyConfig, local_mode_enabled
|
|
942
|
+
from agents.task.agent.core.project_context import build_project_context_message
|
|
943
|
+
_local = local_mode_enabled()
|
|
944
|
+
# Server tier searches the tenant's session workspace, NEVER the process
|
|
945
|
+
# CWD (the install dir). Resolve it via the path manager; on any failure
|
|
946
|
+
# leave it None so server-mode loads nothing rather than the install dir.
|
|
947
|
+
_workspace_dir = None
|
|
948
|
+
if not _local:
|
|
949
|
+
try:
|
|
950
|
+
from agents.task.path import pm
|
|
951
|
+
_workspace_dir = str(pm().get_workspace_dir(
|
|
952
|
+
self.orchestrator.session_id,
|
|
953
|
+
getattr(self.orchestrator, "user_id", None),
|
|
954
|
+
))
|
|
955
|
+
except Exception:
|
|
956
|
+
_workspace_dir = None
|
|
957
|
+
_proj_ctx = build_project_context_message(
|
|
958
|
+
local=_local,
|
|
959
|
+
autoload=AutonomyConfig.project_context_autoload(),
|
|
960
|
+
server_mode=AutonomyConfig.project_context_server_mode(),
|
|
961
|
+
cwd=os.getcwd(),
|
|
962
|
+
workspace_dir=_workspace_dir,
|
|
963
|
+
cap_tokens=AutonomyConfig.project_context_max_tokens(),
|
|
964
|
+
)
|
|
965
|
+
self.message_manager.set_project_context_message(_proj_ctx)
|
|
966
|
+
except Exception as e:
|
|
967
|
+
self.logger.debug(f"Could not load project context (non-fatal): {e}")
|
|
968
|
+
|
|
969
|
+
# Model-identity SSOT: pin the model/provider the agent actually runs on, so
|
|
970
|
+
# it can answer "what model are you" from an authoritative foundation line
|
|
971
|
+
# instead of reading env/config files (which leaked secrets). Sourced from the
|
|
972
|
+
# MessageManager model_name SSOT + the detected provider. Fail-open/inert.
|
|
973
|
+
try:
|
|
974
|
+
_ident_model = getattr(self.message_manager, "model_name", None) or getattr(self, "model_name", None)
|
|
975
|
+
_ident_provider = (
|
|
976
|
+
getattr(self, "llm_provider", None)
|
|
977
|
+
or getattr(self.message_manager, "provider_name", None)
|
|
978
|
+
)
|
|
979
|
+
self.message_manager.set_runtime_identity(_ident_model, _ident_provider)
|
|
980
|
+
except Exception as e:
|
|
981
|
+
self.logger.debug(f"Could not pin runtime identity (non-fatal): {e}")
|
|
982
|
+
|
|
983
|
+
# A5: optional cheap auxiliary model for compaction only. Inert unless
|
|
984
|
+
# COMPACTION_MODEL is set; on any failure aux_llm stays None and
|
|
985
|
+
# llm_compact_history transparently uses the main model (current behaviour).
|
|
986
|
+
try:
|
|
987
|
+
self.message_manager.aux_llm = self._provision_compaction_llm()
|
|
988
|
+
except Exception as e:
|
|
989
|
+
self.logger.debug(f"compaction aux model provisioning skipped: {e}")
|
|
990
|
+
self.message_manager.aux_llm = None
|
|
991
|
+
|
|
992
|
+
# Reflection LLM: its own aux slot (B5), which inherits compaction's
|
|
993
|
+
# model+provider by default when unset (back-compat: reflection historically
|
|
994
|
+
# reused _provision_compaction_llm wholesale) — see
|
|
995
|
+
# constants.resolve_aux_chain(). Operators can now set AUX_MODEL_REFLECTION /
|
|
996
|
+
# AUX_FALLBACK_REFLECTION independently. UP-09: default ON via
|
|
997
|
+
# constants.reflection_llm_enabled_default() — the SAME helper the
|
|
998
|
+
# TaskContextManager runtime guard reads, so the two can never diverge
|
|
999
|
+
# (historical bug: TCM read BotConfig.get -> always False -> never fired).
|
|
1000
|
+
from agents.task.constants import reflection_llm_enabled_default
|
|
1001
|
+
if reflection_llm_enabled_default():
|
|
1002
|
+
try:
|
|
1003
|
+
self.task_context_manager.reflection_llm = self._provision_aux_llm("reflection")
|
|
1004
|
+
except Exception as e:
|
|
1005
|
+
self.logger.debug(f"reflection LLM provisioning skipped: {e}")
|
|
1006
|
+
|
|
1007
|
+
# CO-F1: the optional cheap aux 'judge' model for output validation
|
|
1008
|
+
# (_validate_output) is now provisioned LAZILY — only on the first actual
|
|
1009
|
+
# _validate_output() call, and only when validate_output is on (mirrors the
|
|
1010
|
+
# BackgroundReviewMixin pattern: aux models are resolved at the point of use,
|
|
1011
|
+
# not unconditionally at construction). Provisioning it here unconditionally
|
|
1012
|
+
# wasted an aux-model resolution on every session even while judge-backed
|
|
1013
|
+
# validation was entirely dead (validate_output defaulted False and nothing
|
|
1014
|
+
# ever flipped it, and the run loop never validated the true final answer).
|
|
1015
|
+
# See OutputValidationMixin._validate_output for the lazy provision site.
|
|
1016
|
+
self._judge_llm = None
|
|
1017
|
+
|
|
1018
|
+
# Log model name from MessageManager (single source of truth)
|
|
1019
|
+
self.logger.debug(f"Using model: {self.message_manager.model_name}")
|
|
1020
|
+
|
|
1021
|
+
if self.available_file_paths:
|
|
1022
|
+
self.message_manager.add_file_paths(self.available_file_paths)
|
|
1023
|
+
# Step callback
|
|
1024
|
+
self.register_new_step_callback = register_new_step_callback
|
|
1025
|
+
self.register_done_callback = register_done_callback
|
|
1026
|
+
|
|
1027
|
+
# Centralized agent state - MUST BE INITIALIZED EARLY
|
|
1028
|
+
from agents.task.agent.agent_state import AgentState
|
|
1029
|
+
self.state = AgentState(
|
|
1030
|
+
n_steps=1,
|
|
1031
|
+
max_steps=None, # Set later if profile provides it
|
|
1032
|
+
max_failures=max_failures,
|
|
1033
|
+
consecutive_failures=0,
|
|
1034
|
+
total_actions_count=0,
|
|
1035
|
+
paused=False,
|
|
1036
|
+
stopped=False
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
# Update activity time now that state exists (used for stall detection)
|
|
1040
|
+
self.state.last_activity_time = time.time()
|
|
1041
|
+
self.state.last_step_start_time = time.time()
|
|
1042
|
+
|
|
1043
|
+
# RESTORATION: Try to load saved agent state from previous session
|
|
1044
|
+
if self.session_id and orchestrator:
|
|
1045
|
+
try:
|
|
1046
|
+
from agents.task.path import pm
|
|
1047
|
+
state_file = pm().create_file_path(
|
|
1048
|
+
session_id=self.session_id,
|
|
1049
|
+
subdir_name="data",
|
|
1050
|
+
filename="agent_state.json",
|
|
1051
|
+
user_id=orchestrator.user_id if hasattr(orchestrator, 'user_id') else None
|
|
1052
|
+
)
|
|
1053
|
+
|
|
1054
|
+
loaded_state = AgentState.load_from_file(state_file)
|
|
1055
|
+
if loaded_state:
|
|
1056
|
+
self.state = loaded_state
|
|
1057
|
+
self.logger.info(f"📂 Restored agent state from previous session: step {self.state.n_steps}")
|
|
1058
|
+
|
|
1059
|
+
# Update max_failures if current config provides new value
|
|
1060
|
+
if max_failures and max_failures != self.state.max_failures:
|
|
1061
|
+
self.state.max_failures = max_failures
|
|
1062
|
+
except Exception as e:
|
|
1063
|
+
self.logger.debug(f"No agent state to restore (new session or error): {e}")
|
|
1064
|
+
|
|
1065
|
+
# Initialize workspace context for file upload awareness
|
|
1066
|
+
from agents.task.workspace_context import get_workspace_context
|
|
1067
|
+
self.workspace_context = get_workspace_context()
|
|
1068
|
+
|
|
1069
|
+
# Tracking variables
|
|
1070
|
+
self.history: AgentHistoryList = AgentHistoryList(history=[])
|
|
1071
|
+
|
|
1072
|
+
self.max_failures = max_failures
|
|
1073
|
+
self.retry_delay = retry_delay
|
|
1074
|
+
self.validate_output = validate_output
|
|
1075
|
+
self.initial_actions = self._convert_initial_actions(initial_actions) if initial_actions else None
|
|
1076
|
+
if save_conversation_path:
|
|
1077
|
+
self.logger.info(f'Saving conversation to {save_conversation_path}')
|
|
1078
|
+
|
|
1079
|
+
# Stall detection tracking
|
|
1080
|
+
self._last_action_count = 0
|
|
1081
|
+
self._stall_check_interval = 30.0 # Check every 30 seconds
|
|
1082
|
+
self._stall_check_task = None
|
|
1083
|
+
self._llm_call_in_progress = False # Track when waiting for LLM response
|
|
1084
|
+
self._llm_call_start_time = None # When the current LLM call started
|
|
1085
|
+
|
|
1086
|
+
# Log registered commands
|
|
1087
|
+
# Use Controller's high-level API instead of directly accessing registry
|
|
1088
|
+
registered_commands = self.controller.get_action_names()
|
|
1089
|
+
self.logger.debug(f"Registered commands: {len(registered_commands)} commands - {', '.join(registered_commands)}")
|
|
1090
|
+
|
|
1091
|
+
# Create a session data dictionary for later use
|
|
1092
|
+
self.session_data = {
|
|
1093
|
+
"agent_id": self.agent_id,
|
|
1094
|
+
"task": self.task,
|
|
1095
|
+
"model_name": self.model_name,
|
|
1096
|
+
"created_at": datetime.now().isoformat(),
|
|
1097
|
+
"steps": [],
|
|
1098
|
+
"llm_requests": []
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
# Native tools debug flag
|
|
1102
|
+
self._native_tools_debug = os.environ.get('NATIVE_TOOLS_DEBUG', '').lower() == 'true'
|
|
1103
|
+
if self._native_tools_debug:
|
|
1104
|
+
self.logger.setLevel(logging.DEBUG)
|
|
1105
|
+
self.logger.info("[NATIVE_TOOLS] Debug mode enabled")
|
|
1106
|
+
|
|
1107
|
+
# Loop detection variables with improved thresholds
|
|
1108
|
+
self._previous_actions = deque(maxlen=LoopDetectionConfig.MEMORY_WINDOW_SIZE) # Use consistent window size
|
|
1109
|
+
self._action_repetition_counter = 0
|
|
1110
|
+
self._max_allowed_repetitions = LoopDetectionConfig.MAX_ALLOWED_REPETITIONS
|
|
1111
|
+
self._last_browser_states = deque(maxlen=3) # MODIFIED: Use bounded deque instead of list
|
|
1112
|
+
self._unchanged_state_count = 0
|
|
1113
|
+
self._state_change_threshold = LoopDetectionConfig.STATE_CHANGE_THRESHOLD
|
|
1114
|
+
|
|
1115
|
+
# No need for these locks that can cause deadlocks
|
|
1116
|
+
|
|
1117
|
+
# FIXED: Initialize memory management for bounded collections
|
|
1118
|
+
self._initialize_memory_management()
|
|
1119
|
+
|
|
1120
|
+
# Get usage tracker if available (UNIFIED tracking - replaces usage_meter + telemetry)
|
|
1121
|
+
# This is the preferred method for token tracking
|
|
1122
|
+
if self.orchestrator and hasattr(self.orchestrator, 'usage_tracker'):
|
|
1123
|
+
self.usage_tracker = self.orchestrator.usage_tracker
|
|
1124
|
+
if self.usage_tracker:
|
|
1125
|
+
self.logger.info("✓ Unified usage tracker initialized (replaces meter + telemetry)")
|
|
1126
|
+
else:
|
|
1127
|
+
self.logger.debug("Unified usage tracker not available")
|
|
1128
|
+
else:
|
|
1129
|
+
self.usage_tracker = None
|
|
1130
|
+
|
|
1131
|
+
# A3: give the compactor + TaskContextManager (reflection) the metering
|
|
1132
|
+
# context so aux LLM calls bill through the same record_llm_usage() path
|
|
1133
|
+
# as the main step. Placed AFTER self.usage_tracker is assigned above
|
|
1134
|
+
# (not next to the :874/:886 aux-model provisioning, which runs before
|
|
1135
|
+
# usage_tracker exists) so these reads are never a stale None.
|
|
1136
|
+
self.message_manager.usage_tracker = self.usage_tracker
|
|
1137
|
+
self.message_manager.metering_user_id = getattr(self, "user_id", None)
|
|
1138
|
+
self.message_manager.metering_agent_id = getattr(self, "agent_id", None)
|
|
1139
|
+
# Capture the MAIN event loop here (construction runs on it). Reflection
|
|
1140
|
+
# consolidation runs on a worker thread (add_step_memory is offloaded via
|
|
1141
|
+
# asyncio.to_thread), where run_coroutine_sync would spin a THROWAWAY loop;
|
|
1142
|
+
# the usage tracker's DB connection binds an asyncio.Lock to the main loop,
|
|
1143
|
+
# so metering on a throwaway loop raises "bound to a different event loop"
|
|
1144
|
+
# and the fail-open swallow means reflection was NEVER billed. Passing the
|
|
1145
|
+
# main loop lets ReflectionService schedule the meter back onto it via
|
|
1146
|
+
# run_coroutine_threadsafe (the main loop is free — only the worker blocks —
|
|
1147
|
+
# so no deadlock). Fail-open: None => legacy run_coroutine_sync path.
|
|
1148
|
+
try:
|
|
1149
|
+
_main_loop = asyncio.get_running_loop()
|
|
1150
|
+
except RuntimeError:
|
|
1151
|
+
_main_loop = None
|
|
1152
|
+
self.task_context_manager.reflection_meter_ctx = {
|
|
1153
|
+
"usage_tracker": self.usage_tracker,
|
|
1154
|
+
"user_id": getattr(self, "user_id", None),
|
|
1155
|
+
"session_id": getattr(self, "session_id", ""),
|
|
1156
|
+
"agent_id": getattr(self, "agent_id", "") or "",
|
|
1157
|
+
"loop": _main_loop,
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
# UsageMeter retired (C5) — LLMUsageTracker (self.usage_tracker, above) is
|
|
1161
|
+
# always available under the same precondition usage_meter needed
|
|
1162
|
+
# (db + balance_manager). Kept as None for back-compat attribute access.
|
|
1163
|
+
self.usage_meter = None
|
|
1164
|
+
|
|
1165
|
+
# Save initial task to task.json for webview chat
|
|
1166
|
+
self._save_initial_task()
|
|
1167
|
+
|
|
1168
|
+
# OPTIMIZATION: Setup tool output logging (Task 1 - Nov 14, 2025)
|
|
1169
|
+
self.tool_output_log_path = None
|
|
1170
|
+
if self.orchestrator and self.orchestrator.session_id and self.orchestrator.user_id:
|
|
1171
|
+
log_dir = (Path(self.orchestrator.session_path)
|
|
1172
|
+
if hasattr(self.orchestrator, 'session_path')
|
|
1173
|
+
else self.orchestrator._path_manager.get_subdir(
|
|
1174
|
+
self.orchestrator.session_id, "logs", self.orchestrator.user_id))
|
|
1175
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
1176
|
+
self.tool_output_log_path = log_dir / "tool_outputs.jsonl"
|
|
1177
|
+
self.logger.debug(f"Tool output logging enabled: {self.tool_output_log_path}")
|
|
1178
|
+
|
|
1179
|
+
|