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
tools/coding/edit.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Pure str_replace logic for the coding tool (C1).
|
|
2
|
+
|
|
3
|
+
Exact-match, unique-or-fail: a 0-match or ambiguous (>1) replace FAILS LOUDLY
|
|
4
|
+
rather than silently editing the wrong place. ``replace_all`` opts into replacing
|
|
5
|
+
every occurrence. No file I/O here — the tool layer owns read/write + path
|
|
6
|
+
confinement; this stays a pure, trivially-testable string transform.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# NOTE: deliberately NO ``from __future__ import annotations`` — keep this module
|
|
10
|
+
# consistent with the coding-tool package landmine rule (registry param-model
|
|
11
|
+
# introspection). It costs nothing here.
|
|
12
|
+
|
|
13
|
+
import re as _re
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EditError(Exception):
|
|
17
|
+
"""Raised when a str_replace cannot be applied unambiguously."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def apply_str_replace(
|
|
21
|
+
content: str,
|
|
22
|
+
old_string: str,
|
|
23
|
+
new_string: str,
|
|
24
|
+
replace_all: bool = False,
|
|
25
|
+
) -> str:
|
|
26
|
+
"""Return ``content`` with ``old_string`` replaced by ``new_string``.
|
|
27
|
+
|
|
28
|
+
Raises :class:`EditError` when ``old_string`` is absent, identical to
|
|
29
|
+
``new_string``, or non-unique without ``replace_all``.
|
|
30
|
+
"""
|
|
31
|
+
if old_string == new_string:
|
|
32
|
+
raise EditError("old_string and new_string are identical; nothing to change")
|
|
33
|
+
count = content.count(old_string)
|
|
34
|
+
if count == 0:
|
|
35
|
+
raise EditError("old_string not found in file")
|
|
36
|
+
if count > 1 and not replace_all:
|
|
37
|
+
raise EditError(
|
|
38
|
+
f"old_string is not unique ({count} matches) — add surrounding context "
|
|
39
|
+
f"to make it unique, or pass replace_all=true"
|
|
40
|
+
)
|
|
41
|
+
if replace_all:
|
|
42
|
+
return content.replace(old_string, new_string)
|
|
43
|
+
return content.replace(old_string, new_string, 1)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_HUNK_RE = _re.compile(r"^@@ -(\d+)(?:,\d+)? \+\d+(?:,\d+)? @@")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def apply_patch(content: str, patch: str) -> str:
|
|
50
|
+
"""Apply a unified-diff ``patch`` to ``content`` and return the new text.
|
|
51
|
+
|
|
52
|
+
Reject-on-context-mismatch: every context (' ') and removed ('-') line must
|
|
53
|
+
match the source at the hunk location, or :class:`EditError` is raised. The
|
|
54
|
+
``@@ -start,len +start,len @@`` header locates the apply point (1-based).
|
|
55
|
+
Lines starting with '+' are inserted; '\\ No newline...' and blank trailing
|
|
56
|
+
lines are ignored. Single-file only (the tool layer owns one file per call).
|
|
57
|
+
"""
|
|
58
|
+
original = content.split("\n")
|
|
59
|
+
patch_lines = patch.split("\n")
|
|
60
|
+
result: list[str] = []
|
|
61
|
+
src_idx = 0
|
|
62
|
+
i = 0
|
|
63
|
+
saw_hunk = False
|
|
64
|
+
while i < len(patch_lines):
|
|
65
|
+
header = _HUNK_RE.match(patch_lines[i])
|
|
66
|
+
if not header:
|
|
67
|
+
i += 1
|
|
68
|
+
continue
|
|
69
|
+
saw_hunk = True
|
|
70
|
+
start = int(header.group(1)) - 1 # 0-based
|
|
71
|
+
if start < src_idx:
|
|
72
|
+
raise EditError("overlapping or out-of-order hunks")
|
|
73
|
+
if start > len(original):
|
|
74
|
+
raise EditError(f"hunk start {start + 1} is beyond end of file")
|
|
75
|
+
result.extend(original[src_idx:start])
|
|
76
|
+
src_idx = start
|
|
77
|
+
i += 1
|
|
78
|
+
while i < len(patch_lines) and not _HUNK_RE.match(patch_lines[i]):
|
|
79
|
+
pl = patch_lines[i]
|
|
80
|
+
if pl.startswith("\\"): # ""
|
|
81
|
+
i += 1
|
|
82
|
+
continue
|
|
83
|
+
if pl.startswith(" "):
|
|
84
|
+
if src_idx >= len(original) or original[src_idx] != pl[1:]:
|
|
85
|
+
found = original[src_idx] if src_idx < len(original) else "<EOF>"
|
|
86
|
+
raise EditError(
|
|
87
|
+
f"context mismatch at line {src_idx + 1}: "
|
|
88
|
+
f"expected {pl[1:]!r}, found {found!r}"
|
|
89
|
+
)
|
|
90
|
+
result.append(original[src_idx])
|
|
91
|
+
src_idx += 1
|
|
92
|
+
elif pl.startswith("-"):
|
|
93
|
+
if src_idx >= len(original) or original[src_idx] != pl[1:]:
|
|
94
|
+
found = original[src_idx] if src_idx < len(original) else "<EOF>"
|
|
95
|
+
raise EditError(
|
|
96
|
+
f"removed-line mismatch at line {src_idx + 1}: "
|
|
97
|
+
f"expected {pl[1:]!r}, found {found!r}"
|
|
98
|
+
)
|
|
99
|
+
src_idx += 1
|
|
100
|
+
elif pl.startswith("+"):
|
|
101
|
+
result.append(pl[1:])
|
|
102
|
+
# bare "" (patch tail) and any other line: ignore
|
|
103
|
+
i += 1
|
|
104
|
+
if not saw_hunk:
|
|
105
|
+
raise EditError("no @@ hunk header found in patch")
|
|
106
|
+
result.extend(original[src_idx:])
|
|
107
|
+
return "\n".join(result)
|
tools/coding/search.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Pure grep/search logic for the coding tool (C2).
|
|
2
|
+
|
|
3
|
+
Python ``re`` walk over a directory tree, gitignore-aware, glob-scoped, bounded.
|
|
4
|
+
No subprocess here so the behaviour is deterministic and unit-testable; the tool
|
|
5
|
+
layer MAY prefer a ``ripgrep`` subprocess as a speed optimisation but this Python
|
|
6
|
+
walk is the correctness SSOT and fallback.
|
|
7
|
+
|
|
8
|
+
Gitignore support is a pragmatic subset: it always skips VCS/build noise
|
|
9
|
+
(``.git``/``__pycache__``/``node_modules``/``venv``/``.venv``/``.mypy_cache``)
|
|
10
|
+
and honours the *literal* entries + simple ``dir/`` and ``*.ext`` globs found in a
|
|
11
|
+
root-level ``.gitignore``. It is NOT a full gitignore implementation (no nested
|
|
12
|
+
ignore files, no negation) — documented intentionally.
|
|
13
|
+
"""
|
|
14
|
+
import fnmatch
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
|
|
19
|
+
_ALWAYS_SKIP_DIRS = {
|
|
20
|
+
".git", "__pycache__", "node_modules", "venv", ".venv",
|
|
21
|
+
".mypy_cache", ".pytest_cache", ".ruff_cache", ".tox",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class Match:
|
|
27
|
+
path: str
|
|
28
|
+
line_no: int
|
|
29
|
+
line: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _load_gitignore(root):
|
|
33
|
+
"""Return (dir_names, glob_patterns) parsed from a root-level .gitignore."""
|
|
34
|
+
dir_names, globs = set(), []
|
|
35
|
+
gi = os.path.join(root, ".gitignore")
|
|
36
|
+
if not os.path.isfile(gi):
|
|
37
|
+
return dir_names, globs
|
|
38
|
+
try:
|
|
39
|
+
for raw in open(gi, "r", encoding="utf-8", errors="ignore"):
|
|
40
|
+
line = raw.strip()
|
|
41
|
+
if not line or line.startswith("#") or line.startswith("!"):
|
|
42
|
+
continue
|
|
43
|
+
line = line.rstrip("/").lstrip("/") if line.endswith("/") else line.lstrip("/")
|
|
44
|
+
if "/" in line:
|
|
45
|
+
# only support a simple leading "dir/..." -> ignore the top dir segment
|
|
46
|
+
line = line.split("/", 1)[0]
|
|
47
|
+
if any(ch in line for ch in "*?["):
|
|
48
|
+
globs.append(line)
|
|
49
|
+
else:
|
|
50
|
+
dir_names.add(line)
|
|
51
|
+
except OSError:
|
|
52
|
+
pass
|
|
53
|
+
return dir_names, globs
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _is_ignored(name, ignored_dirs, ignored_globs):
|
|
57
|
+
if name in ignored_dirs:
|
|
58
|
+
return True
|
|
59
|
+
return any(fnmatch.fnmatch(name, g) for g in ignored_globs)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def search_files(
|
|
63
|
+
root,
|
|
64
|
+
pattern,
|
|
65
|
+
*,
|
|
66
|
+
glob=None,
|
|
67
|
+
output_mode="content",
|
|
68
|
+
max_results=200,
|
|
69
|
+
respect_gitignore=True,
|
|
70
|
+
):
|
|
71
|
+
"""Search ``root`` recursively for ``pattern`` (regex).
|
|
72
|
+
|
|
73
|
+
output_mode="content" -> list[Match]; "files" -> list[str] of unique paths.
|
|
74
|
+
Bounded by ``max_results``. Binary/undecodable files are skipped.
|
|
75
|
+
"""
|
|
76
|
+
rx = re.compile(pattern)
|
|
77
|
+
ignored_dirs, ignored_globs = (
|
|
78
|
+
_load_gitignore(root) if respect_gitignore else (set(), [])
|
|
79
|
+
)
|
|
80
|
+
matches = []
|
|
81
|
+
seen_files = []
|
|
82
|
+
seen_set = set()
|
|
83
|
+
|
|
84
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
85
|
+
# prune ignored / noise directories in-place
|
|
86
|
+
dirnames[:] = [
|
|
87
|
+
d for d in dirnames
|
|
88
|
+
if d not in _ALWAYS_SKIP_DIRS
|
|
89
|
+
and not (respect_gitignore and _is_ignored(d, ignored_dirs, ignored_globs))
|
|
90
|
+
]
|
|
91
|
+
for fn in filenames:
|
|
92
|
+
if respect_gitignore and _is_ignored(fn, ignored_dirs, ignored_globs):
|
|
93
|
+
continue
|
|
94
|
+
if glob and not fnmatch.fnmatch(fn, glob):
|
|
95
|
+
continue
|
|
96
|
+
full = os.path.join(dirpath, fn)
|
|
97
|
+
try:
|
|
98
|
+
with open(full, "r", encoding="utf-8") as f:
|
|
99
|
+
for i, line in enumerate(f, start=1):
|
|
100
|
+
if rx.search(line):
|
|
101
|
+
if output_mode == "files":
|
|
102
|
+
if full not in seen_set:
|
|
103
|
+
seen_set.add(full)
|
|
104
|
+
seen_files.append(full)
|
|
105
|
+
if len(seen_files) >= max_results:
|
|
106
|
+
return seen_files
|
|
107
|
+
break
|
|
108
|
+
matches.append(Match(path=full, line_no=i, line=line.rstrip("\n")))
|
|
109
|
+
if len(matches) >= max_results:
|
|
110
|
+
return matches
|
|
111
|
+
except (OSError, UnicodeDecodeError):
|
|
112
|
+
continue
|
|
113
|
+
return seen_files if output_mode == "files" else matches
|
tools/coding/tool.py
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
"""``coding`` tool (H10-B / H9 SPEC §4): str_replace / grep / run_tests.
|
|
2
|
+
|
|
3
|
+
A minimal single-user coding surface. Edits are confined to the session workspace
|
|
4
|
+
(or CWD) root; ``str_replace`` is exact + unique-or-fail; ``grep`` is the pure
|
|
5
|
+
gitignore-aware search; ``run_tests`` routes through the existing code_exec backend
|
|
6
|
+
(inherits its pgroup-kill timeout + output cap + secret-free env).
|
|
7
|
+
|
|
8
|
+
LANDMINE: NO ``from __future__ import annotations`` in this module — it holds the
|
|
9
|
+
action closures whose param models the registry resolves.
|
|
10
|
+
"""
|
|
11
|
+
import asyncio
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel, Field
|
|
18
|
+
|
|
19
|
+
from tools.base_tool import BaseTool
|
|
20
|
+
from tools.coding.edit import apply_str_replace, apply_patch, EditError
|
|
21
|
+
from tools.coding.search import search_files
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CodingError(Exception):
|
|
25
|
+
"""Raised for confinement / IO errors at the tool boundary."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StrReplaceParams(BaseModel):
|
|
29
|
+
file_path: str = Field(..., description="Path to the file to edit (relative to the workspace, or absolute within it)")
|
|
30
|
+
old_string: str = Field(..., description="Exact text to replace; must be unique unless replace_all=true")
|
|
31
|
+
new_string: str = Field(..., description="Replacement text")
|
|
32
|
+
replace_all: bool = Field(False, description="Replace every occurrence instead of failing on ambiguity")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ApplyPatchParams(BaseModel):
|
|
36
|
+
file_path: str = Field(..., description="Path to the file to patch (relative to the workspace, or absolute within it)")
|
|
37
|
+
patch: str = Field(..., description="Unified diff for this one file (@@ hunks). Context/removed lines must match exactly.")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class GrepParams(BaseModel):
|
|
41
|
+
pattern: str = Field(..., description="Regex to search for")
|
|
42
|
+
path: Optional[str] = Field(None, description="Subdir (relative to workspace) to search; defaults to the whole workspace")
|
|
43
|
+
glob: Optional[str] = Field(None, description="Filename glob filter, e.g. '*.py'")
|
|
44
|
+
output_mode: str = Field("content", description="'content' (path:line:text) or 'files' (matching paths only)")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class RunTestsParams(BaseModel):
|
|
48
|
+
command: Optional[str] = Field(None, description="Test command to run; defaults to 'pytest -q'")
|
|
49
|
+
timeout: Optional[float] = Field(None, description="Wall-clock seconds (clamped by the code_exec backend)")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CreateFileParams(BaseModel):
|
|
53
|
+
file_path: str = Field(..., description="Path of the new file (relative to the workspace)")
|
|
54
|
+
content: str = Field("", description="Initial file content")
|
|
55
|
+
overwrite: bool = Field(False, description="Allow overwriting an existing file")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class MoveFileParams(BaseModel):
|
|
59
|
+
src_path: str = Field(..., description="Existing path to move (relative to the workspace)")
|
|
60
|
+
dest_path: str = Field(..., description="Destination path (relative to the workspace)")
|
|
61
|
+
overwrite: bool = Field(False, description="Allow overwriting an existing destination")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class DeleteFileParams(BaseModel):
|
|
65
|
+
file_path: str = Field(..., description="Path to delete (relative to the workspace)")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class CodingTool(BaseTool):
|
|
69
|
+
"""Edit/search/test the workspace. Gated by CODING_TOOLS_ENABLED."""
|
|
70
|
+
|
|
71
|
+
def __init__(self, name: str = "coding", config=None, container=None):
|
|
72
|
+
super().__init__(name=name, config=config, container=container)
|
|
73
|
+
self._backend = None
|
|
74
|
+
# P1-B F7b: mirrors CodeExecutionTool._get_backend's session-scoped cache
|
|
75
|
+
# (see _get_code_exec_backend below) — a PERSISTENT backend is bound to
|
|
76
|
+
# ONE session's container and must never be shared across sessions the
|
|
77
|
+
# way `self._backend` shares the ephemeral one.
|
|
78
|
+
self._persistent_backends: dict = {}
|
|
79
|
+
self._persistent_lock = asyncio.Lock()
|
|
80
|
+
self._root_override = None
|
|
81
|
+
|
|
82
|
+
# --- root + confinement --------------------------------------------------
|
|
83
|
+
|
|
84
|
+
def _resolve_root(self, execution_context=None) -> str:
|
|
85
|
+
if self._root_override:
|
|
86
|
+
return os.path.abspath(self._root_override)
|
|
87
|
+
try:
|
|
88
|
+
sid = getattr(execution_context, "session_id", None) or getattr(self, "session_id", None)
|
|
89
|
+
if sid:
|
|
90
|
+
from agents.task.path import pm
|
|
91
|
+
return str(pm().get_workspace_dir(sid))
|
|
92
|
+
except Exception:
|
|
93
|
+
pass
|
|
94
|
+
return os.getcwd()
|
|
95
|
+
|
|
96
|
+
def _confine(self, file_path: str, root: str) -> str:
|
|
97
|
+
target = os.path.abspath(os.path.join(root, file_path))
|
|
98
|
+
# Confine on realpath (both sides) so an in-root symlink can't redirect a
|
|
99
|
+
# write outside; operate on the lexical target so a legitimate in-root
|
|
100
|
+
# symlink keeps its semantics. Reuses the shared helper (single source).
|
|
101
|
+
from core.path_safety import is_within_root
|
|
102
|
+
if not is_within_root(target, root):
|
|
103
|
+
raise CodingError(f"path escapes the workspace root: {file_path}")
|
|
104
|
+
# Secret-content guard: refuse credential-shaped targets (.env*, *.pem,
|
|
105
|
+
# config/.env.*, …) even when in-root. Under POLYROB_LOCAL the workspace is
|
|
106
|
+
# the project cwd, so confinement alone can't stop a coding action from
|
|
107
|
+
# reading/rewriting a config/.env.production that lives in the project.
|
|
108
|
+
from agents.task.agent.core.secret_guard import is_credential_file
|
|
109
|
+
if is_credential_file(Path(target)):
|
|
110
|
+
raise CodingError(f"refusing to touch a credential/secret file: {file_path}")
|
|
111
|
+
return target
|
|
112
|
+
|
|
113
|
+
# --- code_exec backend resolution (P1-B F7b) ------------------------------
|
|
114
|
+
|
|
115
|
+
async def _get_code_exec_backend(self, execution_context=None, dev_mode: bool = False):
|
|
116
|
+
"""Resolve the code_exec backend for ``run_tests``.
|
|
117
|
+
|
|
118
|
+
PERSISTENT (opt-in, P1-B F7b): when ``CODE_EXEC_DOCKER_PERSISTENT`` is on
|
|
119
|
+
AND ``execution_context`` carries a truthy ``session_id``, resolve via
|
|
120
|
+
``resolve_backend(session_id=sid)`` and cache it PER SESSION — the
|
|
121
|
+
container is created once (one ``setup()`` call) and reused for every
|
|
122
|
+
later ``run_tests`` call in that session. Mirrors
|
|
123
|
+
``tools.code_exec.tool.CodeExecutionTool._get_backend``.
|
|
124
|
+
|
|
125
|
+
EPHEMERAL (default, byte-for-byte unchanged): flag off, or no
|
|
126
|
+
session_id — one process-wide, session-less backend cached on
|
|
127
|
+
``self._backend``, exactly as before this change.
|
|
128
|
+
|
|
129
|
+
``dev_mode`` (WS-1): a posture-entitled call caches a DEV persistent
|
|
130
|
+
backend under ``(sid, True)`` (writable ``/install`` mounted at setup) so
|
|
131
|
+
a pytest installed by ``run_code(packages=[...])`` is importable here.
|
|
132
|
+
Non-dev keeps the legacy ``resolve_backend(session_id=sid)`` call shape.
|
|
133
|
+
"""
|
|
134
|
+
from tools.code_exec import resolve_backend, code_exec_docker_persistent_enabled
|
|
135
|
+
|
|
136
|
+
sid = None
|
|
137
|
+
if code_exec_docker_persistent_enabled():
|
|
138
|
+
sid = getattr(execution_context, "session_id", None) or None
|
|
139
|
+
|
|
140
|
+
if sid:
|
|
141
|
+
key = (sid, bool(dev_mode))
|
|
142
|
+
cached = self._persistent_backends.get(key)
|
|
143
|
+
if cached is not None:
|
|
144
|
+
return cached
|
|
145
|
+
async with self._persistent_lock:
|
|
146
|
+
cached = self._persistent_backends.get(key) # re-check: lost the race?
|
|
147
|
+
if cached is None:
|
|
148
|
+
if dev_mode:
|
|
149
|
+
cached = resolve_backend(session_id=sid, dev_mode=True)
|
|
150
|
+
else:
|
|
151
|
+
cached = resolve_backend(session_id=sid)
|
|
152
|
+
await cached.setup()
|
|
153
|
+
self._persistent_backends[key] = cached
|
|
154
|
+
return cached
|
|
155
|
+
|
|
156
|
+
if self._backend is None:
|
|
157
|
+
backend = resolve_backend()
|
|
158
|
+
await backend.setup()
|
|
159
|
+
self._backend = backend
|
|
160
|
+
return self._backend
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def _ok(content):
|
|
164
|
+
from tools.controller.types import ActionResult
|
|
165
|
+
return ActionResult(extracted_content=content)
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def _err(msg):
|
|
169
|
+
from tools.controller.types import ActionResult
|
|
170
|
+
return ActionResult(error=msg)
|
|
171
|
+
|
|
172
|
+
# --- actions -------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
@BaseTool.action(
|
|
175
|
+
"Replace an exact, unique string in a file (fails loudly on 0 or >1 matches unless replace_all)",
|
|
176
|
+
param_model=StrReplaceParams,
|
|
177
|
+
)
|
|
178
|
+
async def str_replace(self, params: StrReplaceParams, execution_context=None):
|
|
179
|
+
try:
|
|
180
|
+
root = self._resolve_root(execution_context)
|
|
181
|
+
target = self._confine(params.file_path, root)
|
|
182
|
+
if not os.path.isfile(target):
|
|
183
|
+
return self._err(f"file not found: {params.file_path}")
|
|
184
|
+
with open(target, "r", encoding="utf-8") as f:
|
|
185
|
+
content = f.read()
|
|
186
|
+
try:
|
|
187
|
+
updated = apply_str_replace(
|
|
188
|
+
content, params.old_string, params.new_string, params.replace_all
|
|
189
|
+
)
|
|
190
|
+
except EditError as e:
|
|
191
|
+
return self._err(str(e))
|
|
192
|
+
with open(target, "w", encoding="utf-8") as f:
|
|
193
|
+
f.write(updated)
|
|
194
|
+
n = content.count(params.old_string) if params.replace_all else 1
|
|
195
|
+
return self._ok(f"Edited {params.file_path} ({n} replacement{'s' if n != 1 else ''}).")
|
|
196
|
+
except CodingError as e:
|
|
197
|
+
return self._err(str(e))
|
|
198
|
+
except Exception as e:
|
|
199
|
+
getattr(self, "logger", logging.getLogger(__name__)).error(f"str_replace failed: {e}")
|
|
200
|
+
return self._err(f"str_replace failed: {e}")
|
|
201
|
+
|
|
202
|
+
@BaseTool.action(
|
|
203
|
+
"Apply a unified-diff patch to one file (reject-on-context-mismatch; fails loudly if context doesn't match)",
|
|
204
|
+
param_model=ApplyPatchParams,
|
|
205
|
+
)
|
|
206
|
+
async def apply_patch(self, params: ApplyPatchParams, execution_context=None):
|
|
207
|
+
try:
|
|
208
|
+
root = self._resolve_root(execution_context)
|
|
209
|
+
target = self._confine(params.file_path, root)
|
|
210
|
+
if not os.path.isfile(target):
|
|
211
|
+
return self._err(f"file not found: {params.file_path}")
|
|
212
|
+
with open(target, "r", encoding="utf-8") as f:
|
|
213
|
+
content = f.read()
|
|
214
|
+
try:
|
|
215
|
+
updated = apply_patch(content, params.patch)
|
|
216
|
+
except EditError as e:
|
|
217
|
+
return self._err(str(e))
|
|
218
|
+
with open(target, "w", encoding="utf-8") as f:
|
|
219
|
+
f.write(updated)
|
|
220
|
+
return self._ok(f"Patched {params.file_path}.")
|
|
221
|
+
except CodingError as e:
|
|
222
|
+
return self._err(str(e))
|
|
223
|
+
except Exception as e:
|
|
224
|
+
getattr(self, "logger", logging.getLogger(__name__)).error(f"apply_patch failed: {e}")
|
|
225
|
+
return self._err(f"apply_patch failed: {e}")
|
|
226
|
+
|
|
227
|
+
@BaseTool.action(
|
|
228
|
+
"Search files for a regex (gitignore-aware); output_mode 'content' or 'files'",
|
|
229
|
+
param_model=GrepParams,
|
|
230
|
+
)
|
|
231
|
+
async def grep(self, params: GrepParams, execution_context=None):
|
|
232
|
+
try:
|
|
233
|
+
root = self._resolve_root(execution_context)
|
|
234
|
+
search_root = self._confine(params.path, root) if params.path else root
|
|
235
|
+
hits = search_files(
|
|
236
|
+
search_root, params.pattern, glob=params.glob, output_mode=params.output_mode,
|
|
237
|
+
)
|
|
238
|
+
if not hits:
|
|
239
|
+
return self._ok("(no matches)")
|
|
240
|
+
if params.output_mode == "files":
|
|
241
|
+
rels = [os.path.relpath(p, root) for p in hits]
|
|
242
|
+
return self._ok("\n".join(rels))
|
|
243
|
+
lines = [f"{os.path.relpath(h.path, root)}:{h.line_no}:{h.line}" for h in hits]
|
|
244
|
+
return self._ok("\n".join(lines))
|
|
245
|
+
except CodingError as e:
|
|
246
|
+
return self._err(str(e))
|
|
247
|
+
except Exception as e:
|
|
248
|
+
getattr(self, "logger", logging.getLogger(__name__)).error(f"grep failed: {e}")
|
|
249
|
+
return self._err(f"grep failed: {e}")
|
|
250
|
+
|
|
251
|
+
@BaseTool.action(
|
|
252
|
+
"Run the test suite (default 'pytest -q') in the workspace via the code_exec backend "
|
|
253
|
+
"(sandbox-gated on servers; local convenience under POLYROB_LOCAL)",
|
|
254
|
+
param_model=RunTestsParams,
|
|
255
|
+
)
|
|
256
|
+
async def run_tests(self, params: RunTestsParams, execution_context=None):
|
|
257
|
+
from tools.code_exec.sandbox_guard import code_exec_execution_blocked_reason
|
|
258
|
+
blocked = code_exec_execution_blocked_reason()
|
|
259
|
+
if blocked:
|
|
260
|
+
return self._err(blocked)
|
|
261
|
+
try:
|
|
262
|
+
from tools.code_exec.result import ExecutionRequest
|
|
263
|
+
|
|
264
|
+
root = self._resolve_root(execution_context)
|
|
265
|
+
command = params.command or "pytest -q"
|
|
266
|
+
# WS-1: an entitled session runs importable-mode (PYTHONPATH=/install)
|
|
267
|
+
# so packages installed via run_code(packages=[...]) are visible here.
|
|
268
|
+
try:
|
|
269
|
+
from agents.task.constants import compute_posture_allows
|
|
270
|
+
dev_mode = bool(compute_posture_allows(execution_context, 1))
|
|
271
|
+
except Exception:
|
|
272
|
+
dev_mode = False
|
|
273
|
+
backend = await self._get_code_exec_backend(execution_context, dev_mode=dev_mode)
|
|
274
|
+
req = ExecutionRequest(
|
|
275
|
+
language="bash", code=command, timeout=params.timeout, workdir=root,
|
|
276
|
+
dev_mode=dev_mode,
|
|
277
|
+
)
|
|
278
|
+
result = await backend.run(req)
|
|
279
|
+
parts = []
|
|
280
|
+
if result.stdout:
|
|
281
|
+
parts.append(result.stdout)
|
|
282
|
+
if result.stderr:
|
|
283
|
+
parts.append(f"[stderr]\n{result.stderr}")
|
|
284
|
+
if result.truncated:
|
|
285
|
+
parts.append("[output truncated]")
|
|
286
|
+
content = "\n".join(parts) if parts else "(no output)"
|
|
287
|
+
if result.timed_out:
|
|
288
|
+
return self._err(f"tests timed out after {result.duration_sec:.1f}s\n{content}")
|
|
289
|
+
if result.exit_code not in (0, None):
|
|
290
|
+
return self._err(f"tests failed (exit {result.exit_code})\n{content}")
|
|
291
|
+
return self._ok(content)
|
|
292
|
+
except Exception as e:
|
|
293
|
+
getattr(self, "logger", logging.getLogger(__name__)).error(f"run_tests failed: {e}")
|
|
294
|
+
return self._err(f"run_tests failed: {e}")
|
|
295
|
+
|
|
296
|
+
@BaseTool.action(
|
|
297
|
+
"Create a new file in the workspace (fails if it exists unless overwrite=true)",
|
|
298
|
+
param_model=CreateFileParams,
|
|
299
|
+
)
|
|
300
|
+
async def create_file(self, params: CreateFileParams, execution_context=None):
|
|
301
|
+
try:
|
|
302
|
+
root = self._resolve_root(execution_context)
|
|
303
|
+
target = self._confine(params.file_path, root)
|
|
304
|
+
if os.path.isdir(target):
|
|
305
|
+
return self._err(f"path is a directory: {params.file_path}")
|
|
306
|
+
if os.path.exists(target) and not params.overwrite:
|
|
307
|
+
return self._err(f"file already exists: {params.file_path} (set overwrite=true)")
|
|
308
|
+
os.makedirs(os.path.dirname(target) or root, exist_ok=True)
|
|
309
|
+
with open(target, "w", encoding="utf-8") as f:
|
|
310
|
+
f.write(params.content or "")
|
|
311
|
+
return self._ok(f"Created {params.file_path} ({len(params.content or '')} bytes).")
|
|
312
|
+
except CodingError as e:
|
|
313
|
+
return self._err(str(e))
|
|
314
|
+
except Exception as e:
|
|
315
|
+
getattr(self, "logger", logging.getLogger(__name__)).error(f"create_file failed: {e}")
|
|
316
|
+
return self._err(f"create_file failed: {e}")
|
|
317
|
+
|
|
318
|
+
@BaseTool.action("Move/rename a file within the workspace", param_model=MoveFileParams)
|
|
319
|
+
async def move_file(self, params: MoveFileParams, execution_context=None):
|
|
320
|
+
try:
|
|
321
|
+
root = self._resolve_root(execution_context)
|
|
322
|
+
src = self._confine(params.src_path, root)
|
|
323
|
+
dest = self._confine(params.dest_path, root)
|
|
324
|
+
if not os.path.exists(src):
|
|
325
|
+
return self._err(f"source not found: {params.src_path}")
|
|
326
|
+
if os.path.exists(dest) and not params.overwrite:
|
|
327
|
+
return self._err(f"destination exists: {params.dest_path} (set overwrite=true)")
|
|
328
|
+
os.makedirs(os.path.dirname(dest) or root, exist_ok=True)
|
|
329
|
+
import shutil as _shutil
|
|
330
|
+
_shutil.move(src, dest)
|
|
331
|
+
return self._ok(f"Moved {params.src_path} -> {params.dest_path}.")
|
|
332
|
+
except CodingError as e:
|
|
333
|
+
return self._err(str(e))
|
|
334
|
+
except Exception as e:
|
|
335
|
+
getattr(self, "logger", logging.getLogger(__name__)).error(f"move_file failed: {e}")
|
|
336
|
+
return self._err(f"move_file failed: {e}")
|
|
337
|
+
|
|
338
|
+
@BaseTool.action("Delete a file within the workspace", param_model=DeleteFileParams)
|
|
339
|
+
async def delete_file(self, params: DeleteFileParams, execution_context=None):
|
|
340
|
+
try:
|
|
341
|
+
root = self._resolve_root(execution_context)
|
|
342
|
+
target = self._confine(params.file_path, root)
|
|
343
|
+
if not os.path.exists(target):
|
|
344
|
+
return self._err(f"file not found: {params.file_path}")
|
|
345
|
+
if os.path.isdir(target):
|
|
346
|
+
return self._err(f"refusing to delete a directory: {params.file_path}")
|
|
347
|
+
os.remove(target)
|
|
348
|
+
return self._ok(f"Deleted {params.file_path}.")
|
|
349
|
+
except CodingError as e:
|
|
350
|
+
return self._err(str(e))
|
|
351
|
+
except Exception as e:
|
|
352
|
+
getattr(self, "logger", logging.getLogger(__name__)).error(f"delete_file failed: {e}")
|
|
353
|
+
return self._err(f"delete_file failed: {e}")
|