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,271 @@
|
|
|
1
|
+
"""Lazy context references (`@file` / `@folder` / `@diff` / `@url`) — Tier C1.
|
|
2
|
+
|
|
3
|
+
A scoped-down port of Reference's ``agent/context_references.py`` (see
|
|
4
|
+
docs/REFERENCE_VS_ROB_CONTEXT_SYSTEM_2026-06.md §9 C1). Users / callers can embed a
|
|
5
|
+
reference token in their input and have it expanded inline at prompt-assembly time,
|
|
6
|
+
with **soft (25%) and hard (50%) injection caps** against the model context length so
|
|
7
|
+
a single reference can never blow the window.
|
|
8
|
+
|
|
9
|
+
Syntax:
|
|
10
|
+
@file:<path> inline a file's contents
|
|
11
|
+
@folder:<path> inline a directory listing
|
|
12
|
+
@url:<url> inline the text of a URL (best-effort, short timeout)
|
|
13
|
+
@diff inline `git diff` for the repo root
|
|
14
|
+
@diff:<path> inline `git diff` for a path
|
|
15
|
+
|
|
16
|
+
This is opt-in: call ``preprocess_context_references`` on text that may contain refs.
|
|
17
|
+
Honors POLYROB's principle of failing soft — an unresolvable/oversized ref becomes an
|
|
18
|
+
inline note, never an exception.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import logging
|
|
23
|
+
import os
|
|
24
|
+
import re
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Optional
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
# Caps as a fraction of the model context length (mirrors Reference 25% soft / 50% hard).
|
|
31
|
+
_SOFT_INJECT_RATIO = 0.25
|
|
32
|
+
_HARD_INJECT_RATIO = 0.50
|
|
33
|
+
|
|
34
|
+
_REF_RE = re.compile(r"@(file|folder|url|diff)(?::([^\s]+))?")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _is_within_root(path: str, root: str) -> bool:
|
|
38
|
+
"""True iff ``path`` resolves to a location inside ``root`` (no `..`/symlink escape).
|
|
39
|
+
|
|
40
|
+
Thin alias over the shared core.path_safety.is_within_root (single source of
|
|
41
|
+
truth — the coding/filesystem tools confine the same way).
|
|
42
|
+
"""
|
|
43
|
+
from core.path_safety import is_within_root
|
|
44
|
+
return is_within_root(path, root)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _is_safe_url(url: str) -> bool:
|
|
48
|
+
"""Reject SSRF-prone URLs: non-http(s) schemes and private/loopback/link-local hosts.
|
|
49
|
+
|
|
50
|
+
IP literals are range-checked directly; hostnames are best-effort resolved and any
|
|
51
|
+
resolved private/loopback address blocks the URL. Unresolvable hostnames are left
|
|
52
|
+
permitted (the fetch fails soft later) — this guard targets the obvious internal
|
|
53
|
+
targets (127/8, 10/8, 169.254/16 metadata, ::1, localhost).
|
|
54
|
+
"""
|
|
55
|
+
import ipaddress
|
|
56
|
+
from urllib.parse import urlparse
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
parsed = urlparse(url)
|
|
60
|
+
except Exception:
|
|
61
|
+
return False
|
|
62
|
+
if parsed.scheme not in ("http", "https"):
|
|
63
|
+
return False
|
|
64
|
+
host = parsed.hostname
|
|
65
|
+
if not host:
|
|
66
|
+
return False
|
|
67
|
+
low = host.lower()
|
|
68
|
+
if low == "localhost" or low.endswith(".localhost"):
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
def _blocked(ip: "ipaddress._BaseAddress") -> bool:
|
|
72
|
+
return bool(
|
|
73
|
+
ip.is_private or ip.is_loopback or ip.is_link_local
|
|
74
|
+
or ip.is_reserved or ip.is_multicast or ip.is_unspecified
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
return not _blocked(ipaddress.ip_address(host))
|
|
79
|
+
except ValueError:
|
|
80
|
+
pass # not an IP literal -> hostname
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
import socket
|
|
84
|
+
|
|
85
|
+
for info in socket.getaddrinfo(host, None):
|
|
86
|
+
if _blocked(ipaddress.ip_address(info[4][0])):
|
|
87
|
+
return False
|
|
88
|
+
except Exception:
|
|
89
|
+
return True # unresolvable -> let the fetch fail soft
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _load_file(path: str) -> Optional[str]:
|
|
94
|
+
if not os.path.isfile(path):
|
|
95
|
+
return None
|
|
96
|
+
with open(path, "r", encoding="utf-8", errors="replace") as fh:
|
|
97
|
+
return fh.read()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _load_folder(path: str, root: Optional[str] = None) -> Optional[str]:
|
|
101
|
+
if not os.path.isdir(path):
|
|
102
|
+
return None
|
|
103
|
+
entries = []
|
|
104
|
+
for name in sorted(os.listdir(path)):
|
|
105
|
+
full = os.path.join(path, name)
|
|
106
|
+
# Secret guard: omit (or redact) entries that look like credential files.
|
|
107
|
+
try:
|
|
108
|
+
from agents.task.agent.core.secret_guard import is_secret_path as _isp
|
|
109
|
+
_root = Path(root or os.getcwd())
|
|
110
|
+
if not os.path.isdir(full) and _isp(Path(full), root=_root):
|
|
111
|
+
entries.append(f"{name} <redacted secret>")
|
|
112
|
+
continue
|
|
113
|
+
except Exception:
|
|
114
|
+
pass # fail-soft: keep entry if guard raises
|
|
115
|
+
entries.append(f"{name}/" if os.path.isdir(full) else name)
|
|
116
|
+
return "\n".join(entries)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _load_diff(path: Optional[str], root: Optional[str]) -> Optional[str]:
|
|
120
|
+
import subprocess
|
|
121
|
+
|
|
122
|
+
cmd = ["git", "diff"]
|
|
123
|
+
if path:
|
|
124
|
+
cmd.append(path)
|
|
125
|
+
try:
|
|
126
|
+
result = subprocess.run(
|
|
127
|
+
cmd,
|
|
128
|
+
cwd=root or None,
|
|
129
|
+
capture_output=True,
|
|
130
|
+
text=True,
|
|
131
|
+
timeout=10,
|
|
132
|
+
)
|
|
133
|
+
return result.stdout or "(no diff)"
|
|
134
|
+
except Exception as exc:
|
|
135
|
+
logger.debug(f"@diff unavailable: {exc}")
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _load_url(url: str) -> Optional[str]:
|
|
140
|
+
# Route through the SSRF-safe fetcher (per-hop re-validation, IP pinning, NO
|
|
141
|
+
# auto-redirects, byte cap) instead of raw urlopen. urlopen FOLLOWS redirects and
|
|
142
|
+
# re-resolves DNS at connect time, so the one-shot `_is_safe_url` pre-check is
|
|
143
|
+
# bypassed by a 302 -> 169.254.169.254 (cloud metadata) or a DNS-rebinding host.
|
|
144
|
+
# safe_fetch validates and pins EVERY hop, closing that gap. Bridge the async
|
|
145
|
+
# fetcher into this sync callback via the shared background loop.
|
|
146
|
+
try:
|
|
147
|
+
from tools.web_fetch.fetcher import safe_fetch
|
|
148
|
+
from core.async_bridge import run_coroutine_sync
|
|
149
|
+
|
|
150
|
+
result = run_coroutine_sync(
|
|
151
|
+
safe_fetch(url, max_bytes=2_000_000, timeout_sec=10.0),
|
|
152
|
+
timeout=20.0,
|
|
153
|
+
)
|
|
154
|
+
return result.body.decode("utf-8", errors="replace")
|
|
155
|
+
except Exception as exc:
|
|
156
|
+
logger.debug(f"@url unavailable: {exc}")
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _load(kind: str, arg: Optional[str], root: Optional[str]) -> Optional[str]:
|
|
161
|
+
if kind == "diff":
|
|
162
|
+
return _load_diff(arg, root)
|
|
163
|
+
if kind == "url":
|
|
164
|
+
return _load_url(arg) if arg else None
|
|
165
|
+
if not arg:
|
|
166
|
+
return None
|
|
167
|
+
path = arg
|
|
168
|
+
if root and not os.path.isabs(path):
|
|
169
|
+
path = os.path.join(root, path)
|
|
170
|
+
if kind == "file":
|
|
171
|
+
return _load_file(path)
|
|
172
|
+
if kind == "folder":
|
|
173
|
+
return _load_folder(path, root=root)
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def preprocess_context_references(
|
|
178
|
+
text: str,
|
|
179
|
+
root: Optional[str] = None,
|
|
180
|
+
context_length: int = 128_000,
|
|
181
|
+
confine_to_root: bool = False,
|
|
182
|
+
allow_filesystem: bool = True,
|
|
183
|
+
) -> str:
|
|
184
|
+
"""Expand `@file`/`@folder`/`@diff`/`@url` references in ``text`` inline.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
text: the input possibly containing reference tokens.
|
|
188
|
+
root: base directory for relative paths / the git repo for `@diff`.
|
|
189
|
+
context_length: model context window (chars proxy) used to size the caps.
|
|
190
|
+
confine_to_root: when True, refuse `@file`/`@folder` paths that escape ``root``
|
|
191
|
+
and `@url` targets on private/loopback hosts. Use on untrusted intake
|
|
192
|
+
(CLI / A2A). Default False preserves the unconfined behaviour exactly.
|
|
193
|
+
allow_filesystem: when False, refuse ALL filesystem refs (`@file`/`@folder`/
|
|
194
|
+
`@diff`) outright — only `@url` expands. Use for fully remote intake (A2A)
|
|
195
|
+
where there is no trusted session workspace to confine to.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
``text`` with each resolvable reference replaced by an enveloped block, each
|
|
199
|
+
oversized/over-budget reference replaced by an inline note, and unresolvable
|
|
200
|
+
references left untouched.
|
|
201
|
+
"""
|
|
202
|
+
if not text or "@" not in text:
|
|
203
|
+
return text
|
|
204
|
+
|
|
205
|
+
hard_cap = int(context_length * _HARD_INJECT_RATIO)
|
|
206
|
+
soft_cap = int(context_length * _SOFT_INJECT_RATIO)
|
|
207
|
+
injected = {"total": 0}
|
|
208
|
+
|
|
209
|
+
def _expand(match: re.Match) -> str:
|
|
210
|
+
kind = match.group(1)
|
|
211
|
+
arg = match.group(2)
|
|
212
|
+
# strip trailing sentence punctuation that isn't part of a path/url
|
|
213
|
+
if arg:
|
|
214
|
+
arg = arg.rstrip(".,);:")
|
|
215
|
+
|
|
216
|
+
if kind in ("file", "folder", "url") and not arg:
|
|
217
|
+
return match.group(0) # nothing to resolve
|
|
218
|
+
|
|
219
|
+
label = f"{kind}:{arg}" if arg else kind
|
|
220
|
+
|
|
221
|
+
# Remote intake: refuse all server-side filesystem refs outright.
|
|
222
|
+
if not allow_filesystem and kind in ("file", "folder", "diff"):
|
|
223
|
+
return f"[{label}: filesystem references disabled; refused]"
|
|
224
|
+
|
|
225
|
+
# Confinement guard (untrusted intake): refuse escapes / SSRF targets.
|
|
226
|
+
if confine_to_root:
|
|
227
|
+
if kind in ("file", "folder") and arg:
|
|
228
|
+
candidate = arg if os.path.isabs(arg) else os.path.join(root or "", arg)
|
|
229
|
+
if not (root and _is_within_root(candidate, root)):
|
|
230
|
+
return f"[{label}: path outside allowed root; refused]"
|
|
231
|
+
elif kind == "url" and arg and not _is_safe_url(arg):
|
|
232
|
+
return f"[{label}: url blocked (private/loopback or non-http); refused]"
|
|
233
|
+
|
|
234
|
+
# Secret / binary guard — runs for @file regardless of confine_to_root.
|
|
235
|
+
if kind == "file" and arg:
|
|
236
|
+
try:
|
|
237
|
+
from agents.task.agent.core.secret_guard import (
|
|
238
|
+
is_binary_file as _ibf,
|
|
239
|
+
is_secret_path as _isp,
|
|
240
|
+
)
|
|
241
|
+
_resolved = arg if os.path.isabs(arg) else os.path.join(root or os.getcwd(), arg)
|
|
242
|
+
_p = Path(_resolved)
|
|
243
|
+
_r = Path(root or os.getcwd())
|
|
244
|
+
if _isp(_p, root=_r):
|
|
245
|
+
return f"[{label}: sensitive credential file; refused]"
|
|
246
|
+
if _ibf(_p):
|
|
247
|
+
try:
|
|
248
|
+
size = _p.stat().st_size
|
|
249
|
+
return f"[{label}: binary file, {size} bytes; not inlined]"
|
|
250
|
+
except OSError:
|
|
251
|
+
return f"[{label}: binary file; not inlined]"
|
|
252
|
+
except Exception:
|
|
253
|
+
pass # fail-soft: fall through to normal load on guard error
|
|
254
|
+
|
|
255
|
+
content = _load(kind, arg, root)
|
|
256
|
+
if content is None:
|
|
257
|
+
return match.group(0) # unresolvable -> leave the token as-is
|
|
258
|
+
|
|
259
|
+
if len(content) > hard_cap:
|
|
260
|
+
return f"[{label}: {len(content)} chars exceeds hard inject cap ({hard_cap}); not inlined]"
|
|
261
|
+
|
|
262
|
+
remaining = soft_cap - injected["total"]
|
|
263
|
+
if len(content) > remaining:
|
|
264
|
+
if remaining < 200:
|
|
265
|
+
return f"[{label}: {len(content)} chars skipped (soft inject cap reached)]"
|
|
266
|
+
content = content[:remaining] + "\n…[truncated at soft inject cap]"
|
|
267
|
+
|
|
268
|
+
injected["total"] += len(content)
|
|
269
|
+
return f'\n<context-ref kind="{kind}" src="{arg or ""}">\n{content}\n</context-ref>\n'
|
|
270
|
+
|
|
271
|
+
return _REF_RE.sub(_expand, text)
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Dict, List, Optional, Type
|
|
7
|
+
|
|
8
|
+
from modules.llm.messages import (
|
|
9
|
+
AIMessage,
|
|
10
|
+
BaseMessage,
|
|
11
|
+
HumanMessage,
|
|
12
|
+
SystemMessage,
|
|
13
|
+
ToolMessage,
|
|
14
|
+
)
|
|
15
|
+
from agents.task.agent.message_manager.tool_call_builder import (
|
|
16
|
+
ToolCallBuilder,
|
|
17
|
+
detect_and_remove_duplicate_tool_calls,
|
|
18
|
+
repair_and_normalize,
|
|
19
|
+
validate_tool_message_pairs,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def dedup_tool_results(messages: List[BaseMessage]) -> List[BaseMessage]:
|
|
26
|
+
"""Replace byte-identical repeated tool outputs with a back-reference (B2).
|
|
27
|
+
|
|
28
|
+
Reference does this (MD5-keyed) in its compaction pre-pass — cheap token savings on
|
|
29
|
+
retry/polling loops where the same tool returns the same payload repeatedly. The
|
|
30
|
+
FIRST occurrence is kept verbatim; later identical outputs are replaced with a
|
|
31
|
+
short pointer to the first. Non-tool messages and unique outputs pass through
|
|
32
|
+
unchanged. Does not mutate the input list.
|
|
33
|
+
|
|
34
|
+
See docs/REFERENCE_VS_ROB_CONTEXT_SYSTEM_2026-06.md §9 (B2).
|
|
35
|
+
"""
|
|
36
|
+
import hashlib
|
|
37
|
+
|
|
38
|
+
seen: Dict[str, int] = {}
|
|
39
|
+
out: List[BaseMessage] = []
|
|
40
|
+
for msg in messages:
|
|
41
|
+
if isinstance(msg, ToolMessage) and isinstance(msg.content, str) and msg.content:
|
|
42
|
+
digest = hashlib.md5(msg.content.encode("utf-8", "ignore")).hexdigest()[:12]
|
|
43
|
+
if digest in seen:
|
|
44
|
+
ref = ToolMessage(
|
|
45
|
+
content=f"[duplicate of an earlier tool result #{seen[digest]} — output identical to {digest}]",
|
|
46
|
+
tool_call_id=msg.tool_call_id,
|
|
47
|
+
)
|
|
48
|
+
out.append(ref)
|
|
49
|
+
continue
|
|
50
|
+
seen[digest] = len(out)
|
|
51
|
+
out.append(msg)
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _message_has_base64_image(message: BaseMessage) -> bool:
|
|
56
|
+
content = getattr(message, "content", None)
|
|
57
|
+
if not isinstance(content, list):
|
|
58
|
+
return False
|
|
59
|
+
for block in content:
|
|
60
|
+
if (
|
|
61
|
+
isinstance(block, dict)
|
|
62
|
+
and block.get("type") == "image_url"
|
|
63
|
+
and "base64" in str(block.get("image_url", {}).get("url", ""))
|
|
64
|
+
):
|
|
65
|
+
return True
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def strip_historical_media(messages: List[BaseMessage]) -> List[BaseMessage]:
|
|
70
|
+
"""Strip base64 images from every image-bearing turn EXCEPT the most recent (B3).
|
|
71
|
+
|
|
72
|
+
POLYROB's blunt ``STRIP_BASE64_IMAGES`` removes *all* images at parse, which hurts
|
|
73
|
+
multi-step vision tasks. Reference instead anchors on the last image-bearing turn and
|
|
74
|
+
strips base64 only from turns *before* it — preserving vision continuity for the
|
|
75
|
+
current turn while still bounding history growth. Does not mutate the input list.
|
|
76
|
+
|
|
77
|
+
See docs/REFERENCE_VS_ROB_CONTEXT_SYSTEM_2026-06.md §9 (B3).
|
|
78
|
+
"""
|
|
79
|
+
anchor = -1
|
|
80
|
+
for idx, msg in enumerate(messages):
|
|
81
|
+
if _message_has_base64_image(msg):
|
|
82
|
+
anchor = idx
|
|
83
|
+
|
|
84
|
+
if anchor < 0:
|
|
85
|
+
return list(messages)
|
|
86
|
+
|
|
87
|
+
out: List[BaseMessage] = []
|
|
88
|
+
for idx, msg in enumerate(messages):
|
|
89
|
+
if idx < anchor and _message_has_base64_image(msg):
|
|
90
|
+
new_content = []
|
|
91
|
+
for block in msg.content:
|
|
92
|
+
if (
|
|
93
|
+
isinstance(block, dict)
|
|
94
|
+
and block.get("type") == "image_url"
|
|
95
|
+
and "base64" in str(block.get("image_url", {}).get("url", ""))
|
|
96
|
+
):
|
|
97
|
+
new_content.append({"type": "text", "text": "[historical image stripped]"})
|
|
98
|
+
else:
|
|
99
|
+
new_content.append(block)
|
|
100
|
+
stripped = copy.copy(msg)
|
|
101
|
+
stripped.content = new_content
|
|
102
|
+
out.append(stripped)
|
|
103
|
+
else:
|
|
104
|
+
out.append(msg)
|
|
105
|
+
return out
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class FiltersMixin:
|
|
109
|
+
# Empty slots so the composed MessageManager keeps its own __slots__ (no __dict__).
|
|
110
|
+
__slots__ = ()
|
|
111
|
+
|
|
112
|
+
def merge_successive_messages(self, messages: list[BaseMessage], class_to_merge: Type[BaseMessage]) -> list[BaseMessage]:
|
|
113
|
+
"""Merge successive messages without mutating original content
|
|
114
|
+
|
|
115
|
+
IMPORTANT: Messages with tool_calls are never merged to preserve integrity
|
|
116
|
+
"""
|
|
117
|
+
merged_messages = []
|
|
118
|
+
streak = 0
|
|
119
|
+
|
|
120
|
+
for message in messages:
|
|
121
|
+
if isinstance(message, class_to_merge):
|
|
122
|
+
# Never merge AI messages with tool_calls to preserve integrity
|
|
123
|
+
if isinstance(message, AIMessage) and hasattr(message, 'tool_calls') and message.tool_calls:
|
|
124
|
+
merged_messages.append(message)
|
|
125
|
+
streak = 0
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
# Check if previous message has tool_calls (should not merge into it)
|
|
129
|
+
if (streak > 0 and merged_messages and
|
|
130
|
+
isinstance(merged_messages[-1], AIMessage) and
|
|
131
|
+
hasattr(merged_messages[-1], 'tool_calls') and
|
|
132
|
+
merged_messages[-1].tool_calls):
|
|
133
|
+
merged_messages.append(message)
|
|
134
|
+
streak = 1
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
streak += 1
|
|
138
|
+
if streak > 1:
|
|
139
|
+
# Create new message instead of mutating
|
|
140
|
+
if isinstance(message.content, list) and isinstance(merged_messages[-1].content, list):
|
|
141
|
+
# FIXED: Properly handle multimodal content by extending lists instead of concatenating strings
|
|
142
|
+
new_content = merged_messages[-1].content + message.content
|
|
143
|
+
elif isinstance(message.content, list):
|
|
144
|
+
# Previous message was text, current is multimodal
|
|
145
|
+
text_content = merged_messages[-1].content
|
|
146
|
+
new_content = [{"type": "text", "text": text_content}] + message.content
|
|
147
|
+
elif isinstance(merged_messages[-1].content, list):
|
|
148
|
+
# Previous message was multimodal, current is text
|
|
149
|
+
text_part = {"type": "text", "text": message.content}
|
|
150
|
+
new_content = merged_messages[-1].content + [text_part]
|
|
151
|
+
else:
|
|
152
|
+
# Both are text — join with a newline so the two messages' content
|
|
153
|
+
# keeps its boundary (bare concatenation glued "...end""start..." together).
|
|
154
|
+
prev_text = merged_messages[-1].content
|
|
155
|
+
sep = "\n" if prev_text and not prev_text.endswith("\n") else ""
|
|
156
|
+
new_content = prev_text + sep + message.content
|
|
157
|
+
|
|
158
|
+
# Preserve all other fields from the first message when merging
|
|
159
|
+
# This ensures we don't lose important metadata
|
|
160
|
+
first_msg = merged_messages[-1]
|
|
161
|
+
|
|
162
|
+
# Create new message with known supported fields
|
|
163
|
+
# Most chat message classes support these fields
|
|
164
|
+
kwargs = {'content': new_content}
|
|
165
|
+
|
|
166
|
+
# Preserve common optional fields if they exist
|
|
167
|
+
if hasattr(first_msg, 'additional_kwargs') and first_msg.additional_kwargs:
|
|
168
|
+
kwargs['additional_kwargs'] = first_msg.additional_kwargs
|
|
169
|
+
if hasattr(first_msg, 'response_metadata') and first_msg.response_metadata:
|
|
170
|
+
kwargs['response_metadata'] = first_msg.response_metadata
|
|
171
|
+
if hasattr(first_msg, 'id') and first_msg.id:
|
|
172
|
+
kwargs['id'] = first_msg.id
|
|
173
|
+
|
|
174
|
+
# For AI messages, preserve tool_calls (though we shouldn't merge those)
|
|
175
|
+
if isinstance(first_msg, AIMessage):
|
|
176
|
+
if hasattr(first_msg, 'tool_calls') and first_msg.tool_calls:
|
|
177
|
+
kwargs['tool_calls'] = first_msg.tool_calls
|
|
178
|
+
if hasattr(first_msg, 'invalid_tool_calls') and first_msg.invalid_tool_calls:
|
|
179
|
+
kwargs['invalid_tool_calls'] = first_msg.invalid_tool_calls
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
merged_messages[-1] = class_to_merge(**kwargs)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
self.logger.warning(f"Failed to merge messages with kwargs, falling back to content only: {e}")
|
|
185
|
+
# Fallback to just content if the class doesn't support the kwargs
|
|
186
|
+
merged_messages[-1] = class_to_merge(content=new_content)
|
|
187
|
+
else:
|
|
188
|
+
merged_messages.append(message)
|
|
189
|
+
else:
|
|
190
|
+
merged_messages.append(message)
|
|
191
|
+
streak = 0
|
|
192
|
+
|
|
193
|
+
return merged_messages
|
|
194
|
+
|
|
195
|
+
def _filter_sensitive_data(self, message: BaseMessage) -> BaseMessage:
|
|
196
|
+
"""Filter out sensitive data from the message, tool calls, and metadata.
|
|
197
|
+
|
|
198
|
+
Creates a copy of the message to avoid mutating the original.
|
|
199
|
+
"""
|
|
200
|
+
import copy
|
|
201
|
+
from core.env import bool_env
|
|
202
|
+
from core.secret_scrub import scrub_secret_shapes
|
|
203
|
+
|
|
204
|
+
# Phase 0.5: pattern backstop for UNregistered secrets. The allowlist
|
|
205
|
+
# (self.sensitive_data) only redacts explicitly-registered values; an sk-/
|
|
206
|
+
# AKIA/Bearer/PEM leaking through a tool result would otherwise persist to
|
|
207
|
+
# message_history.json + compaction checkpoints in the clear. Default ON;
|
|
208
|
+
# HISTORY_SECRET_SCRUB=off restores allowlist-only. Conservative shapes only
|
|
209
|
+
# (no hex/base64 catch-all) so legitimate working content is never corrupted.
|
|
210
|
+
pattern_scrub_on = bool_env("HISTORY_SECRET_SCRUB", True)
|
|
211
|
+
|
|
212
|
+
def replace_sensitive(value: str) -> str:
|
|
213
|
+
if not isinstance(value, str):
|
|
214
|
+
return value
|
|
215
|
+
if self.sensitive_data:
|
|
216
|
+
for key, val in self.sensitive_data.items():
|
|
217
|
+
value = value.replace(val, f'<secret>{key}</secret>')
|
|
218
|
+
if pattern_scrub_on:
|
|
219
|
+
value = scrub_secret_shapes(value)
|
|
220
|
+
return value
|
|
221
|
+
|
|
222
|
+
def scrub_dict(data: dict) -> dict:
|
|
223
|
+
"""Recursively scrub sensitive data from dictionaries"""
|
|
224
|
+
if not isinstance(data, dict):
|
|
225
|
+
return data
|
|
226
|
+
scrubbed = {}
|
|
227
|
+
for k, v in data.items():
|
|
228
|
+
if isinstance(v, str):
|
|
229
|
+
scrubbed[k] = replace_sensitive(v)
|
|
230
|
+
elif isinstance(v, dict):
|
|
231
|
+
scrubbed[k] = scrub_dict(v)
|
|
232
|
+
elif isinstance(v, list):
|
|
233
|
+
scrubbed[k] = [scrub_dict(item) if isinstance(item, dict) else
|
|
234
|
+
replace_sensitive(item) if isinstance(item, str) else item
|
|
235
|
+
for item in v]
|
|
236
|
+
else:
|
|
237
|
+
scrubbed[k] = v
|
|
238
|
+
return scrubbed
|
|
239
|
+
|
|
240
|
+
# Create a deep copy of the message to avoid mutating the original
|
|
241
|
+
filtered_message = copy.deepcopy(message)
|
|
242
|
+
|
|
243
|
+
# Filter content (string or multimodal)
|
|
244
|
+
if isinstance(filtered_message.content, str):
|
|
245
|
+
filtered_message.content = replace_sensitive(filtered_message.content)
|
|
246
|
+
elif isinstance(filtered_message.content, list):
|
|
247
|
+
for i, item in enumerate(filtered_message.content):
|
|
248
|
+
if isinstance(item, dict) and 'text' in item:
|
|
249
|
+
item['text'] = replace_sensitive(item['text'])
|
|
250
|
+
filtered_message.content[i] = item
|
|
251
|
+
|
|
252
|
+
# Filter tool calls in AIMessage
|
|
253
|
+
if hasattr(filtered_message, 'tool_calls') and filtered_message.tool_calls:
|
|
254
|
+
for tool_call in filtered_message.tool_calls:
|
|
255
|
+
# Scrub args/arguments field
|
|
256
|
+
if isinstance(tool_call, dict):
|
|
257
|
+
if 'args' in tool_call:
|
|
258
|
+
tool_call['args'] = scrub_dict(tool_call['args'])
|
|
259
|
+
if 'arguments' in tool_call:
|
|
260
|
+
# OpenAI format uses 'arguments' as JSON string
|
|
261
|
+
try:
|
|
262
|
+
import json
|
|
263
|
+
args = json.loads(tool_call['arguments'])
|
|
264
|
+
scrubbed_args = scrub_dict(args)
|
|
265
|
+
tool_call['arguments'] = json.dumps(scrubbed_args)
|
|
266
|
+
except:
|
|
267
|
+
# If not JSON, treat as string
|
|
268
|
+
tool_call['arguments'] = replace_sensitive(tool_call['arguments'])
|
|
269
|
+
# Also check nested function field (OpenAI format)
|
|
270
|
+
if 'function' in tool_call and isinstance(tool_call['function'], dict):
|
|
271
|
+
if 'arguments' in tool_call['function']:
|
|
272
|
+
try:
|
|
273
|
+
import json
|
|
274
|
+
args = json.loads(tool_call['function']['arguments'])
|
|
275
|
+
scrubbed_args = scrub_dict(args)
|
|
276
|
+
tool_call['function']['arguments'] = json.dumps(scrubbed_args)
|
|
277
|
+
except:
|
|
278
|
+
tool_call['function']['arguments'] = replace_sensitive(tool_call['function']['arguments'])
|
|
279
|
+
|
|
280
|
+
# Filter ToolMessage content (already handled above for string content)
|
|
281
|
+
# Filter additional_kwargs which might contain sensitive metadata
|
|
282
|
+
if hasattr(filtered_message, 'additional_kwargs') and filtered_message.additional_kwargs:
|
|
283
|
+
filtered_message.additional_kwargs = scrub_dict(filtered_message.additional_kwargs)
|
|
284
|
+
|
|
285
|
+
return filtered_message
|
|
286
|
+
|
|
287
|
+
def _validate_and_repair_tool_sequences(self, messages: List[BaseMessage]) -> List[BaseMessage]:
|
|
288
|
+
"""Validate and repair tool_calls/ToolMessage sequences using centralized logic.
|
|
289
|
+
|
|
290
|
+
✅ FIX (Nov 5, 2025): Now called with conversation-only (no SystemMessage)
|
|
291
|
+
|
|
292
|
+
Delegates to the centralized repair function in tool_call_builder module
|
|
293
|
+
for consistency across the codebase. This is now the SINGLE AUTHORITY
|
|
294
|
+
for all tool message repair, normalization, and validation.
|
|
295
|
+
|
|
296
|
+
NOTE: This is called from get_messages_for_llm() AFTER foundation is built,
|
|
297
|
+
so it only receives conversation messages (no SystemMessage).
|
|
298
|
+
"""
|
|
299
|
+
if not messages:
|
|
300
|
+
return messages
|
|
301
|
+
|
|
302
|
+
# Use the new single-authority repair_and_normalize method
|
|
303
|
+
# expect_system_message=False because we're only repairing conversation
|
|
304
|
+
repaired = repair_and_normalize(messages, logger=self.logger, expect_system_message=False)
|
|
305
|
+
|
|
306
|
+
# Log if message count changed
|
|
307
|
+
if len(repaired) != len(messages):
|
|
308
|
+
self.logger.info(
|
|
309
|
+
f"Tool sequence repair: {len(messages)} messages -> {len(repaired)} messages"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
return repaired
|
|
313
|
+
|
|
314
|
+
def convert_messages_for_non_function_calling_models(self, messages: List[BaseMessage]) -> List[BaseMessage]:
|
|
315
|
+
"""Convert messages for models that don't support native function calling.
|
|
316
|
+
|
|
317
|
+
Converts AIMessage with tool_calls into plain text format.
|
|
318
|
+
"""
|
|
319
|
+
# Message types imported at module level from modules.llm.messages
|
|
320
|
+
converted = []
|
|
321
|
+
|
|
322
|
+
for msg in messages:
|
|
323
|
+
if isinstance(msg, AIMessage) and hasattr(msg, 'tool_calls') and msg.tool_calls:
|
|
324
|
+
# Convert tool calls to text description
|
|
325
|
+
tool_desc = f"Planning to execute: {', '.join([tc.get('name') if isinstance(tc, dict) else tc.name for tc in msg.tool_calls])}"
|
|
326
|
+
converted.append(AIMessage(content=tool_desc))
|
|
327
|
+
elif isinstance(msg, ToolMessage):
|
|
328
|
+
# Convert tool message to human message
|
|
329
|
+
converted.append(HumanMessage(content=f"Result: {msg.content}"))
|
|
330
|
+
else:
|
|
331
|
+
converted.append(msg)
|
|
332
|
+
|
|
333
|
+
return converted
|