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,758 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import copy
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
from collections import deque
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, List, Optional, Type, Tuple, Union
|
|
13
|
+
|
|
14
|
+
from agents.task.agent.views import ActionResult, AgentBrain, AgentOutput, AgentStepInfo
|
|
15
|
+
|
|
16
|
+
# Native message types
|
|
17
|
+
from modules.llm.messages import (
|
|
18
|
+
AIMessage,
|
|
19
|
+
BaseMessage,
|
|
20
|
+
HumanMessage,
|
|
21
|
+
SystemMessage,
|
|
22
|
+
ToolMessage,
|
|
23
|
+
MessageOrigin,
|
|
24
|
+
make_control_message,
|
|
25
|
+
)
|
|
26
|
+
from modules.llm.adapters import BaseChatModel
|
|
27
|
+
|
|
28
|
+
from tools.browser.views import BrowserState
|
|
29
|
+
|
|
30
|
+
# Import centralized LLM functionality - Single source of truth
|
|
31
|
+
try:
|
|
32
|
+
# Use model_registry as the canonical source for model configs
|
|
33
|
+
from modules.llm.model_registry import get_model_config
|
|
34
|
+
from modules.llm import count_tokens, count_messages_tokens
|
|
35
|
+
LLM_MODULE_AVAILABLE = True
|
|
36
|
+
except ImportError:
|
|
37
|
+
LLM_MODULE_AVAILABLE = False
|
|
38
|
+
# Create a logger here since we need it for the warning
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
logger.warning("LLM module not available, using fallback token counting")
|
|
41
|
+
|
|
42
|
+
# Import centralized constants
|
|
43
|
+
from agents.task.constants import IMG_TOKENS
|
|
44
|
+
# Model limits now come from modules.llm.model_registry
|
|
45
|
+
|
|
46
|
+
from agents.task.agent.message_manager.views import MessageHistory, MessageMetadata, ManagedMessage
|
|
47
|
+
from agents.task.agent.message_manager.tool_call_builder import (
|
|
48
|
+
ToolCallBuilder,
|
|
49
|
+
detect_and_remove_duplicate_tool_calls,
|
|
50
|
+
repair_and_normalize,
|
|
51
|
+
validate_tool_message_pairs
|
|
52
|
+
)
|
|
53
|
+
# MessageTrimmer removed - trimming disabled
|
|
54
|
+
from agents.task.agent.message_manager.config import MessageManagerConfig
|
|
55
|
+
|
|
56
|
+
from agents.task.agent.messages.token_counter import TokenCounterMixin
|
|
57
|
+
from agents.task.agent.messages.compactor import CompactorMixin
|
|
58
|
+
from agents.task.agent.messages.persistence import PersistenceMixin
|
|
59
|
+
from agents.task.agent.messages.filters import FiltersMixin
|
|
60
|
+
from agents.task.agent.messages.guidance import GuidanceMixin
|
|
61
|
+
from agents.task.agent.messages.builders import MessageBuildersMixin
|
|
62
|
+
from agents.task.agent.messages.retrieval import MessageRetrievalMixin
|
|
63
|
+
|
|
64
|
+
logger = logging.getLogger(__name__)
|
|
65
|
+
|
|
66
|
+
# Token counting is now centralized in modules/llm
|
|
67
|
+
# No local fallbacks needed
|
|
68
|
+
|
|
69
|
+
class MessageManager(TokenCounterMixin, CompactorMixin, PersistenceMixin, FiltersMixin, GuidanceMixin, MessageBuildersMixin, MessageRetrievalMixin):
|
|
70
|
+
__slots__ = (
|
|
71
|
+
'llm', 'system_prompt_class', 'history', 'task', 'action_descriptions',
|
|
72
|
+
'IMG_TOKENS', 'include_attributes',
|
|
73
|
+
'max_error_length', 'max_actions_per_step',
|
|
74
|
+
'sensitive_data', 'session_id', 'add_task_message',
|
|
75
|
+
'max_input_tokens', 'safe_input_tokens', 'completion_reserve', 'system_prompt',
|
|
76
|
+
'logger', 'use_native_tools',
|
|
77
|
+
'tool_call_tracker', # Tool call tracker for ID management
|
|
78
|
+
'task_context_manager', # Phase 2: Hierarchical memory system
|
|
79
|
+
'compaction_manager', # FIX (Jan 2026): CompactionManager for context-aware compaction
|
|
80
|
+
'mcp_servers', # MCP server info for dynamic prompt generation
|
|
81
|
+
'_ephemeral_messages', # One-shot messages to include on next LLM call
|
|
82
|
+
'_model_name', # Cached model name
|
|
83
|
+
'_provider_name', # Cached provider name
|
|
84
|
+
'_history_checkpoint', # Checkpoint for message history rollback
|
|
85
|
+
'_checkpoint_token_count', # Token count at checkpoint time
|
|
86
|
+
'_system_message', # SystemMessage stored separately to prevent deque eviction
|
|
87
|
+
'_system_message_tokens', # Token count for system message
|
|
88
|
+
'_initial_task_message', # Initial task message stored separately to prevent deque eviction
|
|
89
|
+
'_initial_task_tokens', # Token count for initial task message
|
|
90
|
+
'_skill_message', # PR13: skills injected as a pinned foundation user message (not in system prompt)
|
|
91
|
+
'_skill_message_tokens', # Token cost of the pinned skill message
|
|
92
|
+
'_self_context_message', # polyrob: frozen SOUL/identity doc pinned in the foundation
|
|
93
|
+
'_self_context_tokens', # Token cost of the pinned self-context message
|
|
94
|
+
'_project_context_message', # C9: auto-loaded CLAUDE.md/AGENTS.md frozen foundation message
|
|
95
|
+
'_project_context_tokens', # Token cost of the pinned project-context message
|
|
96
|
+
'_runtime_identity_message', # model/provider the agent actually runs (swap-refreshable)
|
|
97
|
+
'_runtime_identity_tokens', # Token cost of the pinned runtime-identity message
|
|
98
|
+
'_history_lock', # SECURITY FIX: Lock to protect deque operations from race conditions
|
|
99
|
+
'aux_llm', # A5: optional cheap auxiliary model used only for llm_compact_history
|
|
100
|
+
'_compaction_savings', # B4: last-2 compaction savings ratios for anti-thrash
|
|
101
|
+
'_compaction_count', # C2: monotonic index for pre-compaction checkpoint files
|
|
102
|
+
'_compaction_checkpoint_dir', # C2: explicit checkpoint dir override (else pm() history dir)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
llm: BaseChatModel,
|
|
108
|
+
task: str,
|
|
109
|
+
action_descriptions: str,
|
|
110
|
+
system_prompt_class: Type, # Type of SystemPrompt
|
|
111
|
+
max_input_tokens: Optional[int] = None, # None = auto-calculate from model
|
|
112
|
+
image_tokens: int = IMG_TOKENS,
|
|
113
|
+
include_attributes: list[str] = [],
|
|
114
|
+
max_error_length: int = 400,
|
|
115
|
+
max_actions_per_step: int = 10,
|
|
116
|
+
sensitive_data: Optional[Dict[str, str]] = None,
|
|
117
|
+
session_id: Optional[str] = None,
|
|
118
|
+
include_examples: bool = True,
|
|
119
|
+
add_task_message: bool = True,
|
|
120
|
+
system_message: Optional[SystemMessage] = None, # NEW: Accept prebuilt system message
|
|
121
|
+
use_native_tools: bool = True, # Default to True to match Agent and config
|
|
122
|
+
tool_call_tracker=None, # Tool call tracker for ID management
|
|
123
|
+
task_context_manager=None, # Phase 2: Hierarchical memory system
|
|
124
|
+
mcp_servers: Optional[Dict[str, List[str]]] = None, # MCP server info for dynamic prompts
|
|
125
|
+
persona_block: Optional[str] = None, # S1: chat-mode persona for SystemPrompt <identity>
|
|
126
|
+
tool_ids: Optional[List[str]] = None, # Session's loaded tool_ids for config-aware prompt gating
|
|
127
|
+
include_vision: bool = True, # T1-06: session's use_vision → gates the vision prompt section
|
|
128
|
+
):
|
|
129
|
+
# S1 (chat consolidation): persona text forwarded to the SystemPrompt class
|
|
130
|
+
# when this manager builds the system message itself (no prebuilt profile
|
|
131
|
+
# message). None/"" => byte-identical legacy prompt.
|
|
132
|
+
self._persona_block = persona_block
|
|
133
|
+
# Set up logger with session ID
|
|
134
|
+
from agents.task.logging_config import get_task_logger
|
|
135
|
+
self.logger = get_task_logger("messages", session_id)
|
|
136
|
+
|
|
137
|
+
self.llm = llm
|
|
138
|
+
self.system_prompt_class = system_prompt_class
|
|
139
|
+
|
|
140
|
+
# Cache model detection on init (single source of truth)
|
|
141
|
+
self._model_name = self._detect_model_name()
|
|
142
|
+
# Use consolidated provider detection from utils
|
|
143
|
+
from agents.task.utils import detect_llm_provider
|
|
144
|
+
self._provider_name = detect_llm_provider(None, self._model_name)
|
|
145
|
+
|
|
146
|
+
# Calculate token limits from model (SINGLE SOURCE OF TRUTH)
|
|
147
|
+
self.max_input_tokens, self.safe_input_tokens, self.completion_reserve = \
|
|
148
|
+
self._calculate_token_limits(llm, max_input_tokens)
|
|
149
|
+
|
|
150
|
+
# Calculate adaptive message limit based on context window
|
|
151
|
+
# ADAPTIVE SCALING (Nov 4, 2025): Scale message history to model capacity
|
|
152
|
+
# Modern models have 128k-2M token contexts - use them effectively!
|
|
153
|
+
import os
|
|
154
|
+
max_messages_override = os.getenv('TASK_MAX_MESSAGES')
|
|
155
|
+
if max_messages_override:
|
|
156
|
+
# P3: a non-numeric TASK_MAX_MESSAGES used to crash MessageManager.__init__
|
|
157
|
+
# (and thus session creation). Ignore an invalid value with a warning.
|
|
158
|
+
try:
|
|
159
|
+
adaptive_max_messages = int(max_messages_override)
|
|
160
|
+
self.logger.info(f"Using environment override for max messages: {adaptive_max_messages}")
|
|
161
|
+
except ValueError:
|
|
162
|
+
self.logger.warning(
|
|
163
|
+
f"Ignoring non-numeric TASK_MAX_MESSAGES={max_messages_override!r}; "
|
|
164
|
+
"using adaptive default")
|
|
165
|
+
max_messages_override = None
|
|
166
|
+
if not max_messages_override:
|
|
167
|
+
# PHASE 1 FIX: Adaptive scaling based on model context window
|
|
168
|
+
# Average message size: ~160 tokens (system/user/assistant/tool messages)
|
|
169
|
+
# Target: Use 10-20% of context window for message history
|
|
170
|
+
from modules.llm.model_registry import get_model_config
|
|
171
|
+
model_config = get_model_config(self._model_name)
|
|
172
|
+
|
|
173
|
+
if model_config and model_config.context_window:
|
|
174
|
+
context_window = model_config.context_window
|
|
175
|
+
|
|
176
|
+
# Scale based on model capacity
|
|
177
|
+
if context_window >= 1_000_000:
|
|
178
|
+
# Large context models (1M+): Use 10% of window for messages
|
|
179
|
+
# 1M * 0.10 / 160 tokens/msg ≈ 625 messages
|
|
180
|
+
message_budget_pct = 0.10
|
|
181
|
+
self.logger.info(f"Detected large context model ({context_window:,} tokens)")
|
|
182
|
+
elif context_window >= 200_000:
|
|
183
|
+
# Medium-large context models (200K-1M): Use 12%
|
|
184
|
+
message_budget_pct = 0.12
|
|
185
|
+
self.logger.info(f"Detected medium-large context model ({context_window:,} tokens)")
|
|
186
|
+
elif context_window >= 100_000:
|
|
187
|
+
# Medium context models (100K-200K): Use 15%
|
|
188
|
+
message_budget_pct = 0.15
|
|
189
|
+
self.logger.info(f"Detected medium context model ({context_window:,} tokens)")
|
|
190
|
+
else:
|
|
191
|
+
# Small context models (<100K): Use 30%
|
|
192
|
+
message_budget_pct = 0.30
|
|
193
|
+
self.logger.info(f"Detected small context model ({context_window:,} tokens)")
|
|
194
|
+
|
|
195
|
+
# Calculate max messages from budget
|
|
196
|
+
avg_tokens_per_message = 160
|
|
197
|
+
message_token_budget = int(context_window * message_budget_pct)
|
|
198
|
+
adaptive_max_messages = int(message_token_budget / avg_tokens_per_message)
|
|
199
|
+
|
|
200
|
+
# Apply reasonable bounds
|
|
201
|
+
adaptive_max_messages = max(50, min(adaptive_max_messages, 2000))
|
|
202
|
+
|
|
203
|
+
self.logger.info(
|
|
204
|
+
f"Adaptive message history: {adaptive_max_messages} messages "
|
|
205
|
+
f"(~{message_token_budget:,} tokens, {message_budget_pct:.1%} of {context_window:,} window)"
|
|
206
|
+
)
|
|
207
|
+
else:
|
|
208
|
+
# Fallback for unknown models
|
|
209
|
+
adaptive_max_messages = 100
|
|
210
|
+
self.logger.warning(
|
|
211
|
+
f"Could not determine context window for {self._model_name}, "
|
|
212
|
+
f"using fallback: {adaptive_max_messages} messages"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Message History vs H-MEM (complementary systems):
|
|
216
|
+
# - Message History: "Did I already read file X? What was the exact error?"
|
|
217
|
+
# - H-MEM: "What phase am I in? What concepts have I learned?"
|
|
218
|
+
|
|
219
|
+
self.history = MessageHistory(max_messages=adaptive_max_messages)
|
|
220
|
+
self.logger.info(
|
|
221
|
+
f"MessageManager initialized with LIMITED history (max_messages={adaptive_max_messages}). "
|
|
222
|
+
f"Long-term memory: TaskContextManager (hierarchical). "
|
|
223
|
+
f"Token limits: max={self.max_input_tokens}, safe={self.safe_input_tokens}"
|
|
224
|
+
)
|
|
225
|
+
self.task = task
|
|
226
|
+
self.action_descriptions = action_descriptions
|
|
227
|
+
self.IMG_TOKENS = image_tokens
|
|
228
|
+
self.include_attributes = include_attributes
|
|
229
|
+
self.max_error_length = max_error_length
|
|
230
|
+
self.max_actions_per_step = max_actions_per_step
|
|
231
|
+
self.sensitive_data = sensitive_data
|
|
232
|
+
# HISTORY_SECRET_SCRUB (default ON): the pattern backstop for UNregistered
|
|
233
|
+
# secrets lives inside _filter_sensitive_data, but every caller gated it
|
|
234
|
+
# behind `if self.sensitive_data:` — which is empty by default, so the
|
|
235
|
+
# default-ON backstop never ran and sk-/AKIA/Bearer/PEM leaking through a
|
|
236
|
+
# tool result persisted to message_history.json + compaction checkpoints in
|
|
237
|
+
# the clear. Cache the flag once so the write/ephemeral paths can invoke the
|
|
238
|
+
# filter even with no allowlist registered.
|
|
239
|
+
from core.env import bool_env as _bool_env
|
|
240
|
+
self._history_secret_scrub = _bool_env("HISTORY_SECRET_SCRUB", True)
|
|
241
|
+
self.session_id = session_id
|
|
242
|
+
self.add_task_message = add_task_message
|
|
243
|
+
|
|
244
|
+
# Store tool call tracker for ID management
|
|
245
|
+
self.tool_call_tracker = tool_call_tracker
|
|
246
|
+
|
|
247
|
+
# Store task context manager for hierarchical memory (Phase 2)
|
|
248
|
+
self.task_context_manager = task_context_manager
|
|
249
|
+
|
|
250
|
+
# FIX (Jan 2026): Initialize CompactionManager for context-aware compaction
|
|
251
|
+
# This is now the single source of truth for compaction thresholds and logic
|
|
252
|
+
from modules.memory.task.compaction_manager import CompactionManager
|
|
253
|
+
self.compaction_manager = CompactionManager.for_model(self._model_name)
|
|
254
|
+
|
|
255
|
+
# Store MCP server info for dynamic prompt generation
|
|
256
|
+
self.mcp_servers = mcp_servers or {}
|
|
257
|
+
|
|
258
|
+
# Session's loaded tool_ids — lets SystemPrompt gate config-aware sections
|
|
259
|
+
# (e.g. <anysite>, <browser-tools>) on the tools actually loaded this session,
|
|
260
|
+
# not a global flag. None is preserved (NOT collapsed to []): it means the
|
|
261
|
+
# caller never declared a tool set, and SystemPrompt keeps legacy sections on.
|
|
262
|
+
self.tool_ids = list(tool_ids) if tool_ids is not None else None
|
|
263
|
+
|
|
264
|
+
# Store native tools flag for message conversion
|
|
265
|
+
self.use_native_tools = use_native_tools
|
|
266
|
+
|
|
267
|
+
# Initialize ephemeral message buffer (one-shot, not counted toward history)
|
|
268
|
+
self._ephemeral_messages: List[BaseMessage] = []
|
|
269
|
+
|
|
270
|
+
# PR13: skills are injected as a pinned foundation user message (set later),
|
|
271
|
+
# not embedded in the system prompt (keeps the system prompt stable/cacheable).
|
|
272
|
+
self._skill_message: Optional[BaseMessage] = None
|
|
273
|
+
# Token cost of the pinned skill message, kept in the foundation totals so
|
|
274
|
+
# compaction/overflow math sees skills (which left the system prompt in PR13).
|
|
275
|
+
self._skill_message_tokens: int = 0
|
|
276
|
+
|
|
277
|
+
# polyrob Phase C: SOUL/IDENTITY self-context is pinned as a frozen foundation
|
|
278
|
+
# user message (set later), NOT embedded in the system prompt — same rationale
|
|
279
|
+
# as skills (keeps the system prompt stable/cacheable). Empty => inert.
|
|
280
|
+
self._self_context_message: Optional[BaseMessage] = None
|
|
281
|
+
self._self_context_tokens: int = 0
|
|
282
|
+
|
|
283
|
+
# C9: auto-loaded project context (CLAUDE.md/AGENTS.md/.cursorrules) is pinned
|
|
284
|
+
# as a frozen foundation message (set later), CLI-only, default-OFF on server.
|
|
285
|
+
# Placed AFTER self-context and BEFORE the initial task (same pattern as
|
|
286
|
+
# self_context but project-scoped rather than identity-scoped). Empty => inert.
|
|
287
|
+
self._project_context_message: Optional[BaseMessage] = None
|
|
288
|
+
self._project_context_tokens: int = 0
|
|
289
|
+
|
|
290
|
+
# Model-identity SSOT: the model/provider the agent is ACTUALLY running on,
|
|
291
|
+
# pinned as a frozen foundation message so the agent can state it truthfully
|
|
292
|
+
# instead of grepping config/.env (which leaked secrets). Set at session
|
|
293
|
+
# start from the built LLM and refreshed on a live /model swap. Empty => inert.
|
|
294
|
+
self._runtime_identity_message: Optional[BaseMessage] = None
|
|
295
|
+
self._runtime_identity_tokens: int = 0
|
|
296
|
+
|
|
297
|
+
# Initialize checkpoint attributes for message history persistence
|
|
298
|
+
self._history_checkpoint: List[ManagedMessage] = []
|
|
299
|
+
self._checkpoint_token_count: int = 0
|
|
300
|
+
|
|
301
|
+
# SECURITY FIX: Initialize lock to protect deque operations from race conditions
|
|
302
|
+
# This prevents data corruption when list/deque conversions happen during concurrent access
|
|
303
|
+
self._history_lock = threading.RLock() # RLock allows reentrant locking
|
|
304
|
+
|
|
305
|
+
# Initialize system message - CRITICAL FIX: Store separately from history to prevent deque eviction
|
|
306
|
+
# The system message is NOT added to self.history.messages because the deque has a max length
|
|
307
|
+
# and will automatically drop old messages, potentially losing the system message.
|
|
308
|
+
# Instead, we store it separately and prepend it when get_messages() is called.
|
|
309
|
+
if system_message is not None:
|
|
310
|
+
# Use the provided system message directly
|
|
311
|
+
self.logger.debug("Using provided system message from profile")
|
|
312
|
+
self._system_message = system_message
|
|
313
|
+
self.system_prompt = system_message
|
|
314
|
+
else:
|
|
315
|
+
# Create system message from prompt class
|
|
316
|
+
# SystemPrompt accepts action_descriptions, max_actions_per_step, use_native_tools, model_name, provider, and mcp_servers
|
|
317
|
+
# Pass model_name and provider so SystemPrompt can inject model-specific instructions (e.g., for Grok)
|
|
318
|
+
# Pass mcp_servers so SystemPrompt can generate dynamic examples using actual server/tool names
|
|
319
|
+
prompt_instance = self.system_prompt_class(
|
|
320
|
+
self.action_descriptions,
|
|
321
|
+
max_actions_per_step=self.max_actions_per_step,
|
|
322
|
+
use_native_tools=use_native_tools,
|
|
323
|
+
model_name=self._model_name,
|
|
324
|
+
provider=self._provider_name,
|
|
325
|
+
mcp_servers=self.mcp_servers,
|
|
326
|
+
persona_block=self._persona_block,
|
|
327
|
+
tool_ids=self.tool_ids,
|
|
328
|
+
include_vision=include_vision,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# SystemPrompt has get_system_message() method that returns a SystemMessage
|
|
332
|
+
system_message = prompt_instance.get_system_message()
|
|
333
|
+
self._system_message = system_message
|
|
334
|
+
self.system_prompt = system_message
|
|
335
|
+
|
|
336
|
+
# Calculate and store system message token count separately
|
|
337
|
+
self._system_message_tokens = self._count_message_tokens(self._system_message)
|
|
338
|
+
self.logger.debug(f"System message stored separately ({self._system_message_tokens} tokens) - will not be evicted by deque")
|
|
339
|
+
|
|
340
|
+
# CRITICAL FIX: Store initial task separately (like SystemMessage) to prevent deque eviction
|
|
341
|
+
# This ensures the original task is ALWAYS visible to the agent, even after 100+ steps
|
|
342
|
+
# Without this, after 20 messages, the agent loses context of what it's supposed to do
|
|
343
|
+
if self.add_task_message:
|
|
344
|
+
# Check if this is a new session (empty history - system message stored separately)
|
|
345
|
+
history_size = len(self.history.messages)
|
|
346
|
+
|
|
347
|
+
if history_size == 0:
|
|
348
|
+
# New session - store initial task SEPARATELY (not in deque)
|
|
349
|
+
self._initial_task_message = HumanMessage(content=self.task)
|
|
350
|
+
self._initial_task_tokens = self._count_message_tokens(self._initial_task_message)
|
|
351
|
+
self.logger.info(f"Initial task message stored separately ({self._initial_task_tokens} tokens) - will not be evicted by deque")
|
|
352
|
+
else:
|
|
353
|
+
# Continuing session - task already established
|
|
354
|
+
# Set to None so it's not included in get_messages()
|
|
355
|
+
self._initial_task_message = None
|
|
356
|
+
self._initial_task_tokens = 0
|
|
357
|
+
self.logger.info(
|
|
358
|
+
f"Skipping initial task message for continuous execution "
|
|
359
|
+
f"(history already has {history_size} messages)"
|
|
360
|
+
)
|
|
361
|
+
else:
|
|
362
|
+
# Task message disabled
|
|
363
|
+
self._initial_task_message = None
|
|
364
|
+
self._initial_task_tokens = 0
|
|
365
|
+
|
|
366
|
+
# Log token usage at initialization (include all separate storage tokens)
|
|
367
|
+
model_name = self.model_name
|
|
368
|
+
total_tokens = self.history.total_tokens + self._system_message_tokens + self._initial_task_tokens
|
|
369
|
+
self.logger.info(
|
|
370
|
+
f"Initialized MessageManager with {total_tokens} tokens for model {model_name} "
|
|
371
|
+
f"(system: {self._system_message_tokens}, task: {self._initial_task_tokens}, history: {self.history.total_tokens})"
|
|
372
|
+
)
|
|
373
|
+
self.logger.debug(f"Token limit: {self.max_input_tokens}, remaining: {self.max_input_tokens - total_tokens}")
|
|
374
|
+
|
|
375
|
+
def _detect_model_name(self) -> str:
|
|
376
|
+
"""Detect and cache model name from LLM instance (single source of truth)."""
|
|
377
|
+
# Try to get specific model name from various attributes
|
|
378
|
+
for attr in ['model_name', 'model', '_model_name', 'deployment_name']:
|
|
379
|
+
if hasattr(self.llm, attr):
|
|
380
|
+
model = getattr(self.llm, attr, None)
|
|
381
|
+
if model:
|
|
382
|
+
return model
|
|
383
|
+
|
|
384
|
+
# Fallback to provider or class name
|
|
385
|
+
return self._provider_name if hasattr(self, '_provider_name') and self._provider_name != "unknown" else self.llm.__class__.__name__
|
|
386
|
+
|
|
387
|
+
# REMOVED: _detect_provider_name - now using consolidated detect_llm_provider from utils.py
|
|
388
|
+
|
|
389
|
+
@property
|
|
390
|
+
def model_name(self) -> str:
|
|
391
|
+
"""Public accessor for cached model name (single source of truth)."""
|
|
392
|
+
return self._model_name
|
|
393
|
+
|
|
394
|
+
@model_name.setter
|
|
395
|
+
def model_name(self, value: str) -> None:
|
|
396
|
+
"""Update model name and re-detect provider (for LLM fallback scenarios)."""
|
|
397
|
+
self._model_name = value
|
|
398
|
+
# Re-detect provider based on new model name
|
|
399
|
+
from agents.task.utils import detect_llm_provider
|
|
400
|
+
self._provider_name = detect_llm_provider(None, value)
|
|
401
|
+
self.logger.info(f"Model name updated to: {value} (provider: {self._provider_name})")
|
|
402
|
+
|
|
403
|
+
@property
|
|
404
|
+
def provider_name(self) -> str:
|
|
405
|
+
"""Public accessor for cached provider name (single source of truth)."""
|
|
406
|
+
return self._provider_name
|
|
407
|
+
|
|
408
|
+
@provider_name.setter
|
|
409
|
+
def provider_name(self, value: str) -> None:
|
|
410
|
+
"""Set the cached provider name authoritatively (for live model swaps).
|
|
411
|
+
|
|
412
|
+
The ``model_name`` setter re-detects the provider FROM the model name
|
|
413
|
+
(``detect_llm_provider``); this setter lets ``swap_model`` override that
|
|
414
|
+
with the registry-canonical provider label after the model is updated."""
|
|
415
|
+
self._provider_name = value
|
|
416
|
+
|
|
417
|
+
def recalibrate_for_model(self, model_name: str) -> None:
|
|
418
|
+
"""Recompute token budgets + compaction manager for a newly-swapped model.
|
|
419
|
+
|
|
420
|
+
Init-time values (``max_input_tokens``/``safe_input_tokens``/
|
|
421
|
+
``completion_reserve`` and the ``CompactionManager``) are sized for the
|
|
422
|
+
ORIGINAL model. After a live ``swap_model`` they must be re-derived for the
|
|
423
|
+
new model's context window. Reuses ``_calculate_token_limits`` (the init-time
|
|
424
|
+
SSOT formula) so the math isn't duplicated, and rebuilds the compaction
|
|
425
|
+
manager the same way ``__init__`` does. Unlike ``recalibrate_token_counts``
|
|
426
|
+
(shrink-only, for a smaller fallback), this always adopts the new model's
|
|
427
|
+
budget in either direction.
|
|
428
|
+
"""
|
|
429
|
+
self._model_name = model_name
|
|
430
|
+
# Re-derive from the new model (None override => auto-calc from the registry,
|
|
431
|
+
# still honouring the TASK_MAX_INPUT_TOKENS env cap inside the helper).
|
|
432
|
+
self.max_input_tokens, self.safe_input_tokens, self.completion_reserve = \
|
|
433
|
+
self._calculate_token_limits(self.llm, None)
|
|
434
|
+
from modules.memory.task.compaction_manager import CompactionManager
|
|
435
|
+
self.compaction_manager = CompactionManager.for_model(self._model_name)
|
|
436
|
+
self.logger.info(
|
|
437
|
+
f"Recalibrated for swapped model {model_name}: "
|
|
438
|
+
f"max_input={self.max_input_tokens}, safe_input={self.safe_input_tokens}, "
|
|
439
|
+
f"completion_reserve={self.completion_reserve}"
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
@classmethod
|
|
444
|
+
def from_config(
|
|
445
|
+
cls,
|
|
446
|
+
llm: BaseChatModel,
|
|
447
|
+
task: str,
|
|
448
|
+
action_descriptions: str,
|
|
449
|
+
system_prompt_class: Type, # Type of SystemPrompt
|
|
450
|
+
config: MessageManagerConfig,
|
|
451
|
+
logger: Optional[logging.Logger] = None
|
|
452
|
+
) -> 'MessageManager':
|
|
453
|
+
"""Create MessageManager from config object.
|
|
454
|
+
|
|
455
|
+
SINGULAR RESPONSIBILITY: Config object contains ALL settings.
|
|
456
|
+
No fallbacks, no deprecated parameters, no duplication.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
llm: The language model
|
|
460
|
+
task: Task description
|
|
461
|
+
action_descriptions: Available actions
|
|
462
|
+
system_prompt_class: System prompt class
|
|
463
|
+
config: Configuration object with all settings (REQUIRED)
|
|
464
|
+
logger: Optional logger instance for DI
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
Configured MessageManager instance
|
|
468
|
+
"""
|
|
469
|
+
if config is None:
|
|
470
|
+
raise ValueError("config is required - no defaults, explicit configuration only")
|
|
471
|
+
|
|
472
|
+
instance = cls(
|
|
473
|
+
llm=llm,
|
|
474
|
+
task=task,
|
|
475
|
+
action_descriptions=action_descriptions,
|
|
476
|
+
system_prompt_class=system_prompt_class,
|
|
477
|
+
max_input_tokens=config.max_input_tokens,
|
|
478
|
+
max_actions_per_step=config.max_actions_per_step,
|
|
479
|
+
**config.to_dict()
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
# Override logger if provided (DI pattern)
|
|
483
|
+
if logger is not None:
|
|
484
|
+
instance.logger = logger
|
|
485
|
+
|
|
486
|
+
return instance
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def _add_message_with_tokens(self, message: BaseMessage, position: Optional[int] = None, _internal: bool = False) -> None:
|
|
490
|
+
"""Add message with token count metadata and auto-trim if needed.
|
|
491
|
+
|
|
492
|
+
MessageManager keeps ONLY recent conversation for immediate context.
|
|
493
|
+
Long-term memory is handled by TaskContextManager.
|
|
494
|
+
|
|
495
|
+
Args:
|
|
496
|
+
message: The message to add
|
|
497
|
+
position: Optional position to insert at
|
|
498
|
+
_internal: If True, bypass single-writer enforcement for AI/Tool messages (used internally)
|
|
499
|
+
"""
|
|
500
|
+
|
|
501
|
+
# filter out sensitive data from the message. Run when an allowlist is
|
|
502
|
+
# registered OR the HISTORY_SECRET_SCRUB backstop is on (default) — the
|
|
503
|
+
# backstop must scrub unregistered secrets even with an empty allowlist.
|
|
504
|
+
if self.sensitive_data or self._history_secret_scrub:
|
|
505
|
+
message = self._filter_sensitive_data(message)
|
|
506
|
+
|
|
507
|
+
token_count = self._count_message_tokens(message)
|
|
508
|
+
from datetime import datetime
|
|
509
|
+
metadata = MessageMetadata(
|
|
510
|
+
input_tokens=token_count,
|
|
511
|
+
timestamp=datetime.now().isoformat(),
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
# SECURITY FIX: Protect all deque operations with lock to prevent race conditions
|
|
515
|
+
# The list/deque conversion and trimming must be atomic
|
|
516
|
+
with self._history_lock:
|
|
517
|
+
# Add message WITHOUT updating total_tokens (will be done via increment)
|
|
518
|
+
managed_msg = ManagedMessage(message=message, metadata=metadata)
|
|
519
|
+
maxlen = self.history.max_messages
|
|
520
|
+
if position is None:
|
|
521
|
+
# A deque(maxlen).append() auto-evicts the leftmost message when full
|
|
522
|
+
# WITHOUT decrementing total_tokens, and the trim loop below can't
|
|
523
|
+
# compensate (len can never EXCEED maxlen). Subtract the soon-to-be
|
|
524
|
+
# evicted message's tokens first so total_tokens doesn't drift upward.
|
|
525
|
+
msgs = self.history.messages
|
|
526
|
+
if maxlen and msgs and len(msgs) >= maxlen:
|
|
527
|
+
ev = msgs[0]
|
|
528
|
+
if getattr(ev, 'metadata', None):
|
|
529
|
+
self.history.total_tokens = max(
|
|
530
|
+
0, self.history.total_tokens - ev.metadata.input_tokens)
|
|
531
|
+
msgs.append(managed_msg)
|
|
532
|
+
else:
|
|
533
|
+
# FIXED: deque with maxlen doesn't support insert() when full
|
|
534
|
+
# Convert to list, insert, then back to deque
|
|
535
|
+
messages_list = list(self.history.messages)
|
|
536
|
+
messages_list.insert(position, managed_msg)
|
|
537
|
+
# deque(maxlen) construction silently drops the leftmost overflow;
|
|
538
|
+
# decrement those tokens too so total_tokens stays consistent.
|
|
539
|
+
if maxlen and len(messages_list) > maxlen:
|
|
540
|
+
for ev in messages_list[: len(messages_list) - maxlen]:
|
|
541
|
+
if getattr(ev, 'metadata', None):
|
|
542
|
+
self.history.total_tokens = max(
|
|
543
|
+
0, self.history.total_tokens - ev.metadata.input_tokens)
|
|
544
|
+
self.history.messages = deque(messages_list, maxlen=maxlen)
|
|
545
|
+
|
|
546
|
+
# Auto-trim if exceeding limit
|
|
547
|
+
while len(self.history.messages) > self.history.max_messages:
|
|
548
|
+
# Keep system message (index 0) and task message (index 1, if present)
|
|
549
|
+
# Start trimming from index 2 onwards to preserve context structure
|
|
550
|
+
# System: index 0 (always kept)
|
|
551
|
+
# Task: index 1 (kept if it's the initial HumanMessage with task)
|
|
552
|
+
# Hierarchical context: injected dynamically, not in history
|
|
553
|
+
# Recent messages: index 2+ (these get trimmed)
|
|
554
|
+
|
|
555
|
+
# Find the oldest trimmable message (skip system and initial task)
|
|
556
|
+
remove_index = 2 if len(self.history.messages) > 2 else 1
|
|
557
|
+
|
|
558
|
+
# Safety check: don't trim if we only have system + task messages
|
|
559
|
+
if remove_index >= len(self.history.messages):
|
|
560
|
+
self.logger.warning("Cannot trim - would remove essential messages")
|
|
561
|
+
break
|
|
562
|
+
|
|
563
|
+
removed_msg = self.history.messages.pop(remove_index)
|
|
564
|
+
|
|
565
|
+
# Decrement token count for removed message
|
|
566
|
+
if hasattr(removed_msg, 'metadata') and removed_msg.metadata:
|
|
567
|
+
removed_tokens = removed_msg.metadata.input_tokens
|
|
568
|
+
self.history.total_tokens = max(0, self.history.total_tokens - removed_tokens)
|
|
569
|
+
|
|
570
|
+
self.logger.debug(f"Trimmed old message from recent history: {type(removed_msg.message).__name__}")
|
|
571
|
+
|
|
572
|
+
# Use incremental token counting for new message
|
|
573
|
+
self._increment_token_count(token_count)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def invalidate_hmem_cache(self) -> None:
|
|
577
|
+
"""Invalidate the H-MEM context cache.
|
|
578
|
+
|
|
579
|
+
FIX (Dec 12, 2025): Now calls TaskContextManager.invalidate_context_cache()
|
|
580
|
+
to force a fresh hierarchical search on next context injection.
|
|
581
|
+
|
|
582
|
+
Note: The cache now auto-invalidates per step, so this is only needed
|
|
583
|
+
when you want immediate cache invalidation (e.g., after major findings).
|
|
584
|
+
"""
|
|
585
|
+
if self.task_context_manager and self.session_id:
|
|
586
|
+
try:
|
|
587
|
+
self.task_context_manager.invalidate_context_cache(self.session_id)
|
|
588
|
+
except Exception as e:
|
|
589
|
+
self.logger.debug(f"H-MEM cache invalidation failed: {e}")
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def add_message(self, message: BaseMessage, position: Optional[int] = None, _internal: bool = False) -> None:
|
|
593
|
+
"""Add a message to history - public wrapper for _add_message_with_tokens
|
|
594
|
+
|
|
595
|
+
IMPORTANT: This method enforces single-writer constraints for AI and Tool messages.
|
|
596
|
+
Only MessageManager methods (add_model_output, add_tool_response) should add these types.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
message: The message to add
|
|
600
|
+
position: Optional position to insert at
|
|
601
|
+
_internal: Private flag used by internal methods to bypass enforcement
|
|
602
|
+
"""
|
|
603
|
+
# ENFORCEMENT: Single Writer for AI/Tool Messages
|
|
604
|
+
# Prevent external callers from directly adding AI or Tool messages
|
|
605
|
+
# AIMessage, ToolMessage imported at module level from modules.llm.messages
|
|
606
|
+
if not _internal:
|
|
607
|
+
if isinstance(message, (AIMessage, ToolMessage)):
|
|
608
|
+
from core.exceptions import BotError
|
|
609
|
+
|
|
610
|
+
error_msg = (
|
|
611
|
+
f"Direct addition of {type(message).__name__} not allowed. "
|
|
612
|
+
f"Use add_model_output() for AI messages or add_tool_response() for Tool messages."
|
|
613
|
+
)
|
|
614
|
+
self.logger.error(f"[SINGLE_WRITER] {error_msg}")
|
|
615
|
+
|
|
616
|
+
class MessageFlowError(BotError):
|
|
617
|
+
"""Error in message flow control"""
|
|
618
|
+
pass
|
|
619
|
+
|
|
620
|
+
raise MessageFlowError(error_msg)
|
|
621
|
+
|
|
622
|
+
self._add_message_with_tokens(message, position)
|
|
623
|
+
|
|
624
|
+
def add_human_message(self, content: str, position: Optional[int] = None) -> None:
|
|
625
|
+
"""Create a HumanMessage with the given content and add it to history"""
|
|
626
|
+
message = HumanMessage(content=content)
|
|
627
|
+
self._add_message_with_tokens(message, position)
|
|
628
|
+
|
|
629
|
+
def append_user_turn(self, text: str) -> None:
|
|
630
|
+
"""Append a plain user message at the END of history (a conversational turn).
|
|
631
|
+
|
|
632
|
+
Contrast with inject_user_guidance, which inserts at position 1 with
|
|
633
|
+
task-directive framing for the HITL resume path. This is an ordinary
|
|
634
|
+
trailing turn, routed through _add_message_with_tokens so it participates
|
|
635
|
+
in token accounting and overflow trimming like any other user message.
|
|
636
|
+
"""
|
|
637
|
+
self._add_message_with_tokens(HumanMessage(content=text))
|
|
638
|
+
|
|
639
|
+
def set_skill_message(self, content: Optional[str]) -> None:
|
|
640
|
+
"""PR13: pin skills as a foundation user message instead of the system prompt.
|
|
641
|
+
|
|
642
|
+
Stored as a SKILL-origin control message and injected in the foundation
|
|
643
|
+
(after the initial task) by get_messages(). Keeps the system prompt stable
|
|
644
|
+
for prompt caching and keeps injected skills distinguishable from user input.
|
|
645
|
+
Pass falsy content to clear.
|
|
646
|
+
"""
|
|
647
|
+
if content and content.strip():
|
|
648
|
+
self._skill_message = make_control_message(content, MessageOrigin.SKILL)
|
|
649
|
+
self._skill_message_tokens = self._count_message_tokens(self._skill_message)
|
|
650
|
+
else:
|
|
651
|
+
self._skill_message = None
|
|
652
|
+
self._skill_message_tokens = 0
|
|
653
|
+
|
|
654
|
+
def set_self_context_message(self, content: Optional[str]) -> None:
|
|
655
|
+
"""polyrob Phase C: pin the SOUL/IDENTITY self-context as a frozen foundation
|
|
656
|
+
user message instead of the system prompt.
|
|
657
|
+
|
|
658
|
+
Stored as a SELF_CONTEXT-origin control message and injected in the
|
|
659
|
+
foundation (before skills) by get_messages()/get_messages_for_llm(). Keeps the
|
|
660
|
+
system prompt stable for prompt caching and reads as an authoritative
|
|
661
|
+
self-definition block distinct from user input. Set once at session start
|
|
662
|
+
(frozen); pass falsy content to clear (the inert default).
|
|
663
|
+
"""
|
|
664
|
+
if content and content.strip():
|
|
665
|
+
self._self_context_message = make_control_message(content, MessageOrigin.SELF_CONTEXT)
|
|
666
|
+
self._self_context_tokens = self._count_message_tokens(self._self_context_message)
|
|
667
|
+
else:
|
|
668
|
+
self._self_context_message = None
|
|
669
|
+
self._self_context_tokens = 0
|
|
670
|
+
|
|
671
|
+
def set_project_context_message(self, content: Optional[str]) -> None:
|
|
672
|
+
"""C9: pin the auto-loaded project context (CLAUDE.md/AGENTS.md/.cursorrules) as a
|
|
673
|
+
frozen foundation message (CLI-only, default-OFF on server).
|
|
674
|
+
|
|
675
|
+
Stored as a PROJECT_CONTEXT-origin control message and injected in the
|
|
676
|
+
foundation AFTER self-context and BEFORE the initial task by
|
|
677
|
+
get_messages()/get_messages_for_llm(). Keeps the system prompt stable for
|
|
678
|
+
prompt caching. Set once at session start (frozen); pass falsy content to
|
|
679
|
+
clear (the inert default on server / when no context file found).
|
|
680
|
+
"""
|
|
681
|
+
if content and content.strip():
|
|
682
|
+
self._project_context_message = make_control_message(content, MessageOrigin.PROJECT_CONTEXT)
|
|
683
|
+
self._project_context_tokens = self._count_message_tokens(self._project_context_message)
|
|
684
|
+
else:
|
|
685
|
+
self._project_context_message = None
|
|
686
|
+
self._project_context_tokens = 0
|
|
687
|
+
|
|
688
|
+
def set_runtime_identity(self, model: Optional[str], provider: Optional[str]) -> None:
|
|
689
|
+
"""Pin the model/provider the agent is ACTUALLY running on as a foundation message.
|
|
690
|
+
|
|
691
|
+
So the agent can answer "what model are you" truthfully instead of reading
|
|
692
|
+
config/.env (which leaked secrets). Set at session start from the built LLM
|
|
693
|
+
and refreshed on a live /model swap. Falsy model => clear (inert). Kept in the
|
|
694
|
+
foundation (not the system prompt) so the prompt stays cacheable; on a swap the
|
|
695
|
+
refreshed line is one short block, so the cache cost is negligible.
|
|
696
|
+
"""
|
|
697
|
+
if model and str(model).strip():
|
|
698
|
+
prov = str(provider).strip() if provider else "unknown"
|
|
699
|
+
content = (
|
|
700
|
+
f"You are currently running on model \"{model}\" via provider \"{prov}\". "
|
|
701
|
+
"State this if asked which model/provider you are — do NOT read env/config "
|
|
702
|
+
"files to answer; this line is authoritative."
|
|
703
|
+
)
|
|
704
|
+
self._runtime_identity_message = make_control_message(content, MessageOrigin.RUNTIME_IDENTITY)
|
|
705
|
+
self._runtime_identity_tokens = self._count_message_tokens(self._runtime_identity_message)
|
|
706
|
+
else:
|
|
707
|
+
self._runtime_identity_message = None
|
|
708
|
+
self._runtime_identity_tokens = 0
|
|
709
|
+
|
|
710
|
+
def add_file_paths(self, file_paths: list[str]) -> None:
|
|
711
|
+
"""Add file paths message with properly escaped paths.
|
|
712
|
+
|
|
713
|
+
Args:
|
|
714
|
+
file_paths: List of file paths to add
|
|
715
|
+
"""
|
|
716
|
+
# Sanitize file paths to prevent injection
|
|
717
|
+
def sanitize_paths(paths: list[str]) -> list[str]:
|
|
718
|
+
if not paths:
|
|
719
|
+
return []
|
|
720
|
+
return [p.replace('"""', '\\"\\"\\"').replace('\\', '\\\\') for p in paths]
|
|
721
|
+
|
|
722
|
+
safe_paths = sanitize_paths(file_paths)
|
|
723
|
+
content = f'Here are file paths you can use: {safe_paths}'
|
|
724
|
+
msg = HumanMessage(content=content)
|
|
725
|
+
self._add_message_with_tokens(msg)
|
|
726
|
+
|
|
727
|
+
def add_new_task(self, new_task: str) -> None:
|
|
728
|
+
"""Add a new task message with properly escaped task text.
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
new_task: The new task description
|
|
732
|
+
"""
|
|
733
|
+
# Reuse the same escaping function from task_instructions
|
|
734
|
+
def escape_task_text(text: str) -> str:
|
|
735
|
+
if not text:
|
|
736
|
+
return ""
|
|
737
|
+
# Replace potential control sequences
|
|
738
|
+
escaped = (text.replace('"""', '\"\"\"') # Escape triple quotes
|
|
739
|
+
.replace('\\', '\\\\') # Escape backslashes
|
|
740
|
+
.replace('{', '{{') # Escape curly braces for f-strings
|
|
741
|
+
.replace('}', '}}'))
|
|
742
|
+
return escaped
|
|
743
|
+
|
|
744
|
+
# Apply escaping directly instead of extracting from another message
|
|
745
|
+
safe_task = escape_task_text(new_task)
|
|
746
|
+
content = f'Your new ultimate task is: """{safe_task}""". Take the previous context into account and finish your new ultimate task. '
|
|
747
|
+
msg = HumanMessage(content=content)
|
|
748
|
+
self._add_message_with_tokens(msg)
|
|
749
|
+
|
|
750
|
+
def add_plan(self, plan: Optional[str], position: Optional[int] = None) -> None:
|
|
751
|
+
"""Add a plan message as AIMessage.
|
|
752
|
+
|
|
753
|
+
This is an internal method that bypasses enforcement.
|
|
754
|
+
"""
|
|
755
|
+
if plan:
|
|
756
|
+
msg = AIMessage(content=plan)
|
|
757
|
+
self._add_message_with_tokens(msg, position, _internal=True)
|
|
758
|
+
|