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,228 @@
|
|
|
1
|
+
"""Safety + lifecycle-control mixin (roadmap P9 decomposition; code-motion from service.py).
|
|
2
|
+
|
|
3
|
+
The loop-safety (failure/overflow/stall detection, control-flag handling) and
|
|
4
|
+
lifecycle (pause/resume/stop/cancel/reset) concern, moved verbatim off the
|
|
5
|
+
``Agent`` god-file. ``Agent`` composes ``SafetyLifecycleMixin`` so call sites
|
|
6
|
+
(run loop in step.py, and external ``agent.stop()`` / ``agent.reset_for_continuation()``)
|
|
7
|
+
are unchanged.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SafetyLifecycleMixin:
|
|
16
|
+
"""Failure/stall safety + pause/resume/stop/cancel/reset for Agent."""
|
|
17
|
+
|
|
18
|
+
def _too_many_failures(self) -> bool:
|
|
19
|
+
"""Check if we should stop due to too many failures"""
|
|
20
|
+
if self.state.consecutive_failures >= self.max_failures:
|
|
21
|
+
self.logger.error(f'❌ Stopping due to {self.max_failures} consecutive failures', exc_info=True)
|
|
22
|
+
return True
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
def _check_context_overflow(self) -> bool:
|
|
26
|
+
"""Check if context exceeds threshold and trigger emergency prune.
|
|
27
|
+
|
|
28
|
+
SIMPLIFIED (Dec 2025): Uses CONTEXT_OVERFLOW_THRESHOLD from config (default 90%).
|
|
29
|
+
Let context grow naturally, only prune when necessary.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
True if pruning was triggered, False otherwise
|
|
33
|
+
"""
|
|
34
|
+
if not hasattr(self, 'message_manager') or not self.message_manager:
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
from agents.task.robust_parse_config import RobustParseConfig
|
|
38
|
+
|
|
39
|
+
usage_pct = self.message_manager.get_context_usage_percent()
|
|
40
|
+
threshold = RobustParseConfig.CONTEXT_OVERFLOW_THRESHOLD * 100 # Convert to percentage
|
|
41
|
+
|
|
42
|
+
if usage_pct >= threshold:
|
|
43
|
+
self.logger.info(f"📦 Context at {usage_pct:.1f}% (threshold: {threshold:.0f}%) - emergency prune")
|
|
44
|
+
self.message_manager.emergency_context_prune()
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
async def _check_for_stall(self) -> bool:
|
|
50
|
+
"""Check if the agent is stalled and not making progress.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
True if agent is stalled and should be stopped
|
|
54
|
+
"""
|
|
55
|
+
if not self.stall_timeout_seconds:
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
current_time = time.time()
|
|
59
|
+
|
|
60
|
+
# IMPORTANT: Don't report stalls while waiting for LLM response
|
|
61
|
+
# LLM calls can legitimately take 60-200+ seconds for complex tasks
|
|
62
|
+
if self._llm_call_in_progress:
|
|
63
|
+
llm_duration = current_time - (self._llm_call_start_time or current_time)
|
|
64
|
+
# Only log at longer intervals (every 60s) to reduce noise
|
|
65
|
+
if llm_duration > 0 and int(llm_duration) % 60 == 0:
|
|
66
|
+
self.logger.debug(f"⏳ LLM call in progress for {llm_duration:.0f}s - stall check skipped")
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
# Check if we've been inactive for too long (excluding LLM time)
|
|
70
|
+
time_since_activity = current_time - self.state.last_activity_time
|
|
71
|
+
if time_since_activity > self.stall_timeout_seconds:
|
|
72
|
+
self.logger.error(f"Agent stalled: No activity for {time_since_activity:.1f} seconds (limit: {self.stall_timeout_seconds}s)", exc_info=True)
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
# Check if we're stuck on the same step for too long
|
|
76
|
+
if self.state.last_step_start_time:
|
|
77
|
+
time_in_step = current_time - self.state.last_step_start_time
|
|
78
|
+
if time_in_step > self.step_timeout_seconds:
|
|
79
|
+
self.logger.error(f"Agent stalled: Step taking {time_in_step:.1f} seconds (limit: {self.step_timeout_seconds}s)", exc_info=True)
|
|
80
|
+
return True
|
|
81
|
+
|
|
82
|
+
# Check if we're not making action progress
|
|
83
|
+
if self.state.total_actions_count == self._last_action_count:
|
|
84
|
+
# Only warn if we're NOT waiting for LLM
|
|
85
|
+
if not self._llm_call_in_progress:
|
|
86
|
+
self.logger.warning(f"No new actions in last {self._stall_check_interval} seconds")
|
|
87
|
+
else:
|
|
88
|
+
# Update last action count and activity time
|
|
89
|
+
self._last_action_count = self.state.total_actions_count
|
|
90
|
+
self.state.last_activity_time = current_time
|
|
91
|
+
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
async def _stall_monitor_loop(self) -> None:
|
|
95
|
+
"""Background task to monitor for stalls."""
|
|
96
|
+
try:
|
|
97
|
+
while not self.state.stopped:
|
|
98
|
+
await asyncio.sleep(self._stall_check_interval)
|
|
99
|
+
|
|
100
|
+
if self.state.stopped:
|
|
101
|
+
# Don't check for stalls when stopped
|
|
102
|
+
break
|
|
103
|
+
|
|
104
|
+
if await self._check_for_stall():
|
|
105
|
+
self.logger.error("Stall detected - stopping agent", exc_info=True)
|
|
106
|
+
self.state.consecutive_failures = self.max_failures # Force stop
|
|
107
|
+
self.state.stopped = True
|
|
108
|
+
break
|
|
109
|
+
except asyncio.CancelledError:
|
|
110
|
+
self.logger.debug("Stall monitor cancelled")
|
|
111
|
+
except Exception as e:
|
|
112
|
+
self.logger.error(f"Error in stall monitor: {e}", exc_info=True)
|
|
113
|
+
|
|
114
|
+
async def _handle_control_flags(self) -> bool:
|
|
115
|
+
"""Handle stop and done flags. Returns True if execution should stop.
|
|
116
|
+
|
|
117
|
+
NOTE: Pause functionality removed (Dec 2025). The blocking wait loop was
|
|
118
|
+
incompatible with continuous chat design where new messages should be processed.
|
|
119
|
+
|
|
120
|
+
For user interruption, use stop which cleanly exits. Users can send a new
|
|
121
|
+
message to continue with a different task (auto-resume via COMPLETED → RESUMED).
|
|
122
|
+
"""
|
|
123
|
+
# Check state.done to stop execution when agent signals completion
|
|
124
|
+
if self.state.done:
|
|
125
|
+
self.logger.info('✅ Agent execution complete (state.done=True)')
|
|
126
|
+
return True
|
|
127
|
+
if self.state.stopped:
|
|
128
|
+
self.logger.info('⏹️ Agent execution stopped by control flag')
|
|
129
|
+
raise InterruptedError("Agent stopped")
|
|
130
|
+
|
|
131
|
+
# Legacy: treat paused as stopped for backwards compatibility
|
|
132
|
+
# The paused flag is deprecated - use stopped instead
|
|
133
|
+
if self.state.paused:
|
|
134
|
+
self.logger.info('⏹️ Agent paused flag set - treating as stop (pause deprecated)')
|
|
135
|
+
self.state.stopped = True # Normalize to stopped
|
|
136
|
+
raise InterruptedError("Agent stopped (paused flag deprecated)")
|
|
137
|
+
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
def pause(self) -> None:
|
|
141
|
+
"""DEPRECATED: Pause functionality removed. Now calls stop() instead.
|
|
142
|
+
|
|
143
|
+
The PAUSED state was removed because the blocking wait loop was incompatible
|
|
144
|
+
with continuous chat design. Use stop() for user interruption, and send a
|
|
145
|
+
new message to continue with modifications (auto-resume via COMPLETED → RESUMED).
|
|
146
|
+
"""
|
|
147
|
+
self.logger.warning('⚠️ pause() is DEPRECATED - redirecting to stop()')
|
|
148
|
+
self.state.stopped = True # Use stopped instead of paused
|
|
149
|
+
|
|
150
|
+
def resume(self) -> None:
|
|
151
|
+
"""DEPRECATED: Resume functionality removed. Sessions auto-resume on new message.
|
|
152
|
+
|
|
153
|
+
The explicit resume action is no longer needed. When a new message is sent
|
|
154
|
+
to a completed/cancelled session, it automatically resumes via the
|
|
155
|
+
continuous chat flow (COMPLETED → RESUMED → RUNNING).
|
|
156
|
+
"""
|
|
157
|
+
self.logger.warning('⚠️ resume() is DEPRECATED - no action taken')
|
|
158
|
+
# No action needed - sessions auto-resume on new message
|
|
159
|
+
|
|
160
|
+
async def stop(self) -> None:
|
|
161
|
+
"""Stop the agent execution."""
|
|
162
|
+
self.logger.info('⏹️ Stopping agent execution')
|
|
163
|
+
self.state.stopped = True
|
|
164
|
+
|
|
165
|
+
# Browser cleanup - release context through orchestrator
|
|
166
|
+
try:
|
|
167
|
+
# Release through orchestrator (delegates to BrowserManager)
|
|
168
|
+
await self.orchestrator.release_browser_context(self.agent_id, close=True)
|
|
169
|
+
except Exception as e:
|
|
170
|
+
self.logger.debug(f"Browser cleanup failed: {e}")
|
|
171
|
+
|
|
172
|
+
self.logger.info("Agent stopped")
|
|
173
|
+
|
|
174
|
+
def cancel(self) -> None:
|
|
175
|
+
"""Cancel the agent execution immediately.
|
|
176
|
+
|
|
177
|
+
Sets cancellation flag that will be checked in the main run loop,
|
|
178
|
+
causing execution to stop at the next step boundary.
|
|
179
|
+
"""
|
|
180
|
+
self.logger.info('❌ Cancelling agent execution')
|
|
181
|
+
self._cancelled = True
|
|
182
|
+
|
|
183
|
+
def reset_for_continuation(self) -> None:
|
|
184
|
+
"""Reset agent state for continuous chat continuation.
|
|
185
|
+
|
|
186
|
+
Called when reusing an existing agent for a new task phase.
|
|
187
|
+
Resets execution flags while preserving message history and context.
|
|
188
|
+
|
|
189
|
+
This is CRITICAL for continuous chat to work properly:
|
|
190
|
+
- Without this reset, _last_result contains is_done=True from previous run
|
|
191
|
+
- The run() loop checks _last_result and exits immediately if is_done=True
|
|
192
|
+
- User's new message would never be processed
|
|
193
|
+
"""
|
|
194
|
+
self.logger.info('♻️ Resetting agent state for continuation')
|
|
195
|
+
|
|
196
|
+
# Cancel any running stall monitor task from previous run
|
|
197
|
+
# This prevents stale monitors from interfering with new execution
|
|
198
|
+
if hasattr(self, '_stall_check_task') and self._stall_check_task:
|
|
199
|
+
if not self._stall_check_task.done():
|
|
200
|
+
self._stall_check_task.cancel()
|
|
201
|
+
self.logger.debug('Cancelled stale stall monitor task from previous run')
|
|
202
|
+
self._stall_check_task = None
|
|
203
|
+
|
|
204
|
+
# Reset last result - prevents immediate exit due to previous is_done=True
|
|
205
|
+
self._last_result = None
|
|
206
|
+
|
|
207
|
+
# Reset cancellation flag
|
|
208
|
+
self._cancelled = False
|
|
209
|
+
|
|
210
|
+
# Reset execution control flags in AgentState
|
|
211
|
+
self.state.stopped = False
|
|
212
|
+
self.state.done = False
|
|
213
|
+
self.state.paused = False
|
|
214
|
+
|
|
215
|
+
# Reset failure counters for fresh execution
|
|
216
|
+
self.state.consecutive_failures = 0
|
|
217
|
+
|
|
218
|
+
# Reset loop detection state
|
|
219
|
+
self.state.reset_loop_detection()
|
|
220
|
+
|
|
221
|
+
# Update activity time
|
|
222
|
+
self.state.update_activity_time()
|
|
223
|
+
|
|
224
|
+
self.logger.info(
|
|
225
|
+
f'✅ Agent reset complete. State: steps={self.state.n_steps}, '
|
|
226
|
+
f'actions={self.state.total_actions_count}, '
|
|
227
|
+
f'history_messages={len(self.message_manager.history.messages)}'
|
|
228
|
+
)
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""Secret and binary file guard (polyrob context-reference safety).
|
|
2
|
+
|
|
3
|
+
Pure, dependency-free helpers used by context-reference ingestion and knowledge-base
|
|
4
|
+
loaders to decide whether a file is safe to read or ingest. Three concerns:
|
|
5
|
+
|
|
6
|
+
1. ``is_secret_path`` — does this path look like a credential / secret file?
|
|
7
|
+
2. ``is_binary_file`` — is this file binary (unreadable as text)?
|
|
8
|
+
3. ``estimate_tokens_rough`` — cheap token-count estimate for budget checks.
|
|
9
|
+
|
|
10
|
+
No I/O side-effects on ``is_secret_path``; ``is_binary_file`` reads at most 4 096 bytes.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import fnmatch
|
|
15
|
+
import mimetypes
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Iterable
|
|
18
|
+
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
# Secret detection
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
# Name globs matched case-insensitively against the file's basename.
|
|
24
|
+
# Entries that contain a path separator are matched against the path's
|
|
25
|
+
# components (see _matches_path_glob below).
|
|
26
|
+
SECRET_NAME_GLOBS: tuple[str, ...] = (
|
|
27
|
+
".env*",
|
|
28
|
+
"*.pem",
|
|
29
|
+
"*.key",
|
|
30
|
+
"id_rsa",
|
|
31
|
+
"id_ed25519",
|
|
32
|
+
"id_*",
|
|
33
|
+
"*credential*",
|
|
34
|
+
"*secret*",
|
|
35
|
+
"*.p12",
|
|
36
|
+
"*.pfx",
|
|
37
|
+
".netrc",
|
|
38
|
+
".npmrc",
|
|
39
|
+
".pypirc",
|
|
40
|
+
".pgpass",
|
|
41
|
+
"bot.db",
|
|
42
|
+
# polyrob-specific relative-path patterns
|
|
43
|
+
"config/.env.*",
|
|
44
|
+
".polyrob/.env",
|
|
45
|
+
".rob/.env", # legacy home (still holds live secrets via transition fallback)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Tool-facing subset: unambiguous CREDENTIAL filenames only. Used by the
|
|
49
|
+
# file-editing tools (filesystem/coding) to refuse reading/writing secret files
|
|
50
|
+
# that are legitimately IN-ROOT under POLYROB_LOCAL (workspace == project cwd, so
|
|
51
|
+
# confinement can't catch them). Deliberately NARROWER than SECRET_NAME_GLOBS:
|
|
52
|
+
# excludes the broad ``*secret*``/``*credential*`` substrings (would block a
|
|
53
|
+
# Python ``secrets.py``) and the ``data/`` dir rule (a legit project data dir),
|
|
54
|
+
# so ordinary project files stay editable. See is_credential_file().
|
|
55
|
+
CREDENTIAL_NAME_GLOBS: tuple[str, ...] = (
|
|
56
|
+
".env*",
|
|
57
|
+
"*.env", # WS-7: catch `polyrob.env`, `prod.env` etc — the prod env file's
|
|
58
|
+
# basename does NOT start with `.env`, so `.env*` alone missed it,
|
|
59
|
+
# leaving the file that holds AGENT_COMPUTE_POSTURE + approval flags
|
|
60
|
+
# writable by an agent surface (a self-posture-escalation path).
|
|
61
|
+
"*.pem",
|
|
62
|
+
"*.key",
|
|
63
|
+
"*.p12",
|
|
64
|
+
"*.pfx",
|
|
65
|
+
"id_rsa",
|
|
66
|
+
"id_ed25519",
|
|
67
|
+
"id_dsa",
|
|
68
|
+
"id_ecdsa",
|
|
69
|
+
".netrc",
|
|
70
|
+
".npmrc",
|
|
71
|
+
".pypirc",
|
|
72
|
+
".pgpass",
|
|
73
|
+
".htpasswd",
|
|
74
|
+
# polyrob-specific relative-path patterns
|
|
75
|
+
"config/.env.*",
|
|
76
|
+
".polyrob/.env",
|
|
77
|
+
".rob/.env",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# WS-7: absolute system config directories whose contents are HARD-DENIED for any
|
|
81
|
+
# agent-writable surface (esp. the posture-2 `self_env` patch_source, which reaches
|
|
82
|
+
# the install tree). These hold the frozen security flags / secrets; the agent must
|
|
83
|
+
# never edit them. Source files elsewhere under the install tree stay patchable.
|
|
84
|
+
PROTECTED_CONFIG_DIRS: tuple[str, ...] = (
|
|
85
|
+
"/etc/polyrob",
|
|
86
|
+
"/etc/rob",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def is_protected_config_path(path: Path) -> bool:
|
|
91
|
+
"""True if *path* is inside a protected system-config dir (WS-7).
|
|
92
|
+
|
|
93
|
+
Realpath-free NAME/prefix check on the given path (callers that need traversal
|
|
94
|
+
safety realpath first). Complements :func:`is_credential_file` (name-shaped
|
|
95
|
+
secrets anywhere) with a location rule for absolute system config the self_env
|
|
96
|
+
tool could otherwise reach.
|
|
97
|
+
"""
|
|
98
|
+
try:
|
|
99
|
+
s = str(path)
|
|
100
|
+
except Exception:
|
|
101
|
+
return False
|
|
102
|
+
norm = s.replace("\\", "/")
|
|
103
|
+
for d in PROTECTED_CONFIG_DIRS:
|
|
104
|
+
if norm == d or norm.startswith(d + "/"):
|
|
105
|
+
return True
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def is_credential_file(path: Path) -> bool:
|
|
110
|
+
"""Return *True* if *path*'s NAME looks like a credential/secret file.
|
|
111
|
+
|
|
112
|
+
Basename + multi-component path-glob checks against ``CREDENTIAL_NAME_GLOBS``
|
|
113
|
+
only — NO parent-dir (``data/``) rule and NO ``*secret*`` substring, so it is
|
|
114
|
+
safe to apply to arbitrary project files in local mode. This is the guard the
|
|
115
|
+
filesystem/coding tools use to refuse a secret file that lives inside the
|
|
116
|
+
workspace (the gap the confinement floor can't cover when workspace == cwd).
|
|
117
|
+
"""
|
|
118
|
+
name = path.name
|
|
119
|
+
for glob in CREDENTIAL_NAME_GLOBS:
|
|
120
|
+
if "/" not in glob:
|
|
121
|
+
if _matches_name_glob(name, glob):
|
|
122
|
+
return True
|
|
123
|
+
elif _matches_path_glob(path, glob):
|
|
124
|
+
return True
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# Any *directory component* of the path that matches one of these names makes
|
|
129
|
+
# the whole path a secret path.
|
|
130
|
+
SECRET_DIR_PARTS: frozenset[str] = frozenset({
|
|
131
|
+
".ssh",
|
|
132
|
+
".aws",
|
|
133
|
+
".gnupg",
|
|
134
|
+
".kube",
|
|
135
|
+
".docker",
|
|
136
|
+
".azure",
|
|
137
|
+
".config/gh",
|
|
138
|
+
"data",
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _path_parts_lower(path: Path) -> list[str]:
|
|
143
|
+
"""Return the parts of *path* in lower-case (POSIX separators)."""
|
|
144
|
+
return [p.lower() for p in path.parts]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _matches_name_glob(name: str, glob: str) -> bool:
|
|
148
|
+
"""Case-insensitive fnmatch of *name* against *glob*."""
|
|
149
|
+
return fnmatch.fnmatch(name.lower(), glob.lower())
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _matches_path_glob(path: Path, glob: str) -> bool:
|
|
153
|
+
"""Match a multi-component glob like ``config/.env.*`` or ``.rob/.env``.
|
|
154
|
+
|
|
155
|
+
Strategy: slide a window of ``len(glob_parts)`` consecutive path parts and
|
|
156
|
+
check if they join (with ``/``) case-insensitively to the glob.
|
|
157
|
+
"""
|
|
158
|
+
glob_parts = glob.lower().replace("\\", "/").split("/")
|
|
159
|
+
n = len(glob_parts)
|
|
160
|
+
parts = [p.lower() for p in path.parts]
|
|
161
|
+
for i in range(len(parts) - n + 1):
|
|
162
|
+
window = "/".join(parts[i : i + n])
|
|
163
|
+
# Rebuild glob as a single fnmatch pattern with / separators.
|
|
164
|
+
if fnmatch.fnmatch(window, "/".join(glob_parts)):
|
|
165
|
+
return True
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _is_data_db(path: Path) -> bool:
|
|
170
|
+
"""``*.db`` rule: applies only when a parent directory is named ``data``."""
|
|
171
|
+
if not path.name.lower().endswith(".db"):
|
|
172
|
+
return False
|
|
173
|
+
# Check parent directories (not the file itself).
|
|
174
|
+
return any(p.lower() == "data" for p in path.parts[:-1])
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _dir_part_match(path: Path, dir_parts: Iterable[str]) -> bool:
|
|
178
|
+
"""True if any directory *component* of *path* matches a ``dir_parts`` entry.
|
|
179
|
+
|
|
180
|
+
Multi-segment entries (e.g. ``.config/gh``) are matched as a consecutive
|
|
181
|
+
window of path parts joined with ``/``.
|
|
182
|
+
"""
|
|
183
|
+
parts_lower = [p.lower() for p in path.parts[:-1]] # skip the filename
|
|
184
|
+
for dp in dir_parts:
|
|
185
|
+
dp_lower = dp.lower()
|
|
186
|
+
dp_segs = dp_lower.split("/")
|
|
187
|
+
n = len(dp_segs)
|
|
188
|
+
window_pat = "/".join(dp_segs)
|
|
189
|
+
for i in range(len(parts_lower) - n + 1):
|
|
190
|
+
window = "/".join(parts_lower[i : i + n])
|
|
191
|
+
if fnmatch.fnmatch(window, window_pat):
|
|
192
|
+
return True
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def is_secret_path(path: Path, *, root: Path) -> bool: # noqa: ARG001
|
|
197
|
+
"""Return *True* if *path* looks like a credential / secret file.
|
|
198
|
+
|
|
199
|
+
Checks (in order):
|
|
200
|
+
1. Any parent directory component matches ``SECRET_DIR_PARTS``.
|
|
201
|
+
2. The basename matches a simple (no ``/``) ``SECRET_NAME_GLOBS`` entry.
|
|
202
|
+
3. The path contains a multi-component pattern from ``SECRET_NAME_GLOBS``
|
|
203
|
+
(e.g. ``config/.env.production``, ``.rob/.env``).
|
|
204
|
+
4. The ``*.db`` rule: applies only when a parent dir is named ``data``.
|
|
205
|
+
|
|
206
|
+
``root`` is accepted for callers that want to pass it (future: relative-path
|
|
207
|
+
normalisation), but is not required for the current checks.
|
|
208
|
+
"""
|
|
209
|
+
# 1. Parent-directory check
|
|
210
|
+
if _dir_part_match(path, SECRET_DIR_PARTS):
|
|
211
|
+
return True
|
|
212
|
+
|
|
213
|
+
name = path.name
|
|
214
|
+
|
|
215
|
+
# 2. Simple (no-slash) glob matches on the basename
|
|
216
|
+
simple_globs = [g for g in SECRET_NAME_GLOBS if "/" not in g]
|
|
217
|
+
for glob in simple_globs:
|
|
218
|
+
if _matches_name_glob(name, glob):
|
|
219
|
+
return True
|
|
220
|
+
|
|
221
|
+
# 3. Multi-component path patterns
|
|
222
|
+
path_globs = [g for g in SECRET_NAME_GLOBS if "/" in g]
|
|
223
|
+
for glob in path_globs:
|
|
224
|
+
if _matches_path_glob(path, glob):
|
|
225
|
+
return True
|
|
226
|
+
|
|
227
|
+
# 4. *.db inside a `data/` directory
|
|
228
|
+
if _is_data_db(path):
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
# ---------------------------------------------------------------------------
|
|
235
|
+
# Binary detection
|
|
236
|
+
# ---------------------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
# Extensions we always treat as plain text regardless of mime type.
|
|
239
|
+
_TEXT_EXTENSIONS: frozenset[str] = frozenset({
|
|
240
|
+
".txt", ".json", ".yaml", ".yml", ".toml",
|
|
241
|
+
".js", ".ts", ".tsx", ".sh",
|
|
242
|
+
".csv", ".html", ".xml", ".rst",
|
|
243
|
+
".ini", ".cfg", ".sql", ".md", ".py",
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
# Extensions for extractable document formats — callers decide how to read
|
|
247
|
+
# them; we report them as NOT binary so they are not silently skipped.
|
|
248
|
+
_EXTRACTABLE_EXTENSIONS: frozenset[str] = frozenset({
|
|
249
|
+
".pdf", ".docx", ".doc", ".odt",
|
|
250
|
+
".xlsx", ".xls", ".ods",
|
|
251
|
+
".pptx", ".ppt",
|
|
252
|
+
".epub", ".rtf",
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def is_binary_file(path: Path) -> bool:
|
|
257
|
+
"""Return *True* if *path* is a binary file that cannot be decoded as text.
|
|
258
|
+
|
|
259
|
+
Detection order:
|
|
260
|
+
1. Text-extension allowlist → always False (text).
|
|
261
|
+
2. Extractable-document list → False (callers handle extraction).
|
|
262
|
+
3. mimetypes guess: ``text/*`` → False.
|
|
263
|
+
4. Null-byte sniff of the first 4 096 bytes → True if a null byte is found.
|
|
264
|
+
5. Default → False (assume text when uncertain).
|
|
265
|
+
"""
|
|
266
|
+
suffix = path.suffix.lower()
|
|
267
|
+
|
|
268
|
+
if suffix in _TEXT_EXTENSIONS:
|
|
269
|
+
return False
|
|
270
|
+
if suffix in _EXTRACTABLE_EXTENSIONS:
|
|
271
|
+
return False
|
|
272
|
+
|
|
273
|
+
mime, _ = mimetypes.guess_type(str(path))
|
|
274
|
+
if mime and mime.startswith("text/"):
|
|
275
|
+
return False
|
|
276
|
+
|
|
277
|
+
# Null-byte sniff
|
|
278
|
+
try:
|
|
279
|
+
data = path.read_bytes()[:4096]
|
|
280
|
+
except OSError:
|
|
281
|
+
return False # can't read → let the caller handle it
|
|
282
|
+
|
|
283
|
+
return b"\x00" in data
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
# ---------------------------------------------------------------------------
|
|
287
|
+
# Token estimation
|
|
288
|
+
# ---------------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def estimate_tokens_rough(text: str) -> int:
|
|
292
|
+
"""Rough token count: ``max(1, len(text) // 4)`` (chars-per-token ≈ 4)."""
|
|
293
|
+
return max(1, len(text) // 4)
|