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,124 @@
|
|
|
1
|
+
"""WebhookSurface: the shared inbound contract for push-based platforms (WhatsApp,
|
|
2
|
+
BlueBubbles, Telegram-webhook). Owns: signature verify -> fast-200 ack -> parse -> dedup ->
|
|
3
|
+
route -> act. A concrete surface implements only verify/parse/idempotency_key. Fail-open
|
|
4
|
+
end to end: a parse/route fault is logged, never raised to the HTTP layer (so the platform
|
|
5
|
+
gets its 200 and does not retry-storm)."""
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
from core.surfaces.envelopes import InboundMessage
|
|
12
|
+
from core.surfaces.idempotency import IdempotencyStore
|
|
13
|
+
from core.surfaces.dispatcher import route_inbound
|
|
14
|
+
from core.surfaces.serialize import KeyedLock
|
|
15
|
+
from core.surfaces.session_chat_registry import build_session_key
|
|
16
|
+
from core.surfaces.transcription import voice_present, transcribe_inbound_media
|
|
17
|
+
from core.surfaces.voice_guard import voice_needs_guard, voice_unavailable_message
|
|
18
|
+
from core.surfaces.voice_echo import voice_transcript, voice_echo_message
|
|
19
|
+
from agents.task.surface_config import SurfaceConfig
|
|
20
|
+
from surfaces.telegram.harness import act_on_inbound # shared RouteDecision->TaskAgent dispatch
|
|
21
|
+
from surfaces.telegram.inbound import InboundResult # dataclass(.inbound, .decision) — DRY
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class WebhookSurface(ABC):
|
|
27
|
+
def __init__(self, idempotency: IdempotencyStore) -> None:
|
|
28
|
+
self._idem = idempotency
|
|
29
|
+
self._lock = KeyedLock()
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def surface_id(self) -> str: ...
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def verify_signature(self, headers: dict, body: bytes) -> bool: ...
|
|
37
|
+
|
|
38
|
+
def verify_challenge(self, params: dict) -> Optional[str]:
|
|
39
|
+
"""GET verification handshake (e.g. Meta hub.challenge). Default: no handshake."""
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def parse(self, payload: dict) -> List[InboundMessage]: ...
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def idempotency_key(self, inbound: InboundMessage) -> str: ...
|
|
47
|
+
|
|
48
|
+
async def handle_post(self, container, headers: dict, body: bytes, task_agent) -> dict:
|
|
49
|
+
# Lower-case header keys so callers can pass platform headers verbatim.
|
|
50
|
+
h = {str(k).lower(): v for k, v in (headers or {}).items()}
|
|
51
|
+
if not self.verify_signature(h, body):
|
|
52
|
+
logger.warning("%s webhook: signature verification FAILED", self.surface_id)
|
|
53
|
+
return {"ok": False, "error": "bad signature"}
|
|
54
|
+
try:
|
|
55
|
+
payload = json.loads(body or b"{}")
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logger.warning("%s webhook: bad JSON: %s", self.surface_id, e)
|
|
58
|
+
return {"ok": True} # ack so the platform stops retrying garbage
|
|
59
|
+
try:
|
|
60
|
+
messages = self.parse(payload)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
logger.error("%s webhook: parse failed: %s", self.surface_id, e, exc_info=True)
|
|
63
|
+
return {"ok": True}
|
|
64
|
+
for inbound in messages:
|
|
65
|
+
try:
|
|
66
|
+
key = self.idempotency_key(inbound)
|
|
67
|
+
if key and self._idem.seen(key):
|
|
68
|
+
continue
|
|
69
|
+
session_key = build_session_key(inbound.identity.source, inbound.identity.user_id)
|
|
70
|
+
async with self._lock.for_key(session_key):
|
|
71
|
+
# Fix 2b: hydrate surface-specific media (e.g. WA media-id -> bytes) before transcription
|
|
72
|
+
if hasattr(self, "hydrate_media"):
|
|
73
|
+
try:
|
|
74
|
+
await self.hydrate_media(inbound.media)
|
|
75
|
+
except Exception:
|
|
76
|
+
pass
|
|
77
|
+
# Fix 2a: transcribe voice-only turns; guard untranscribed voice (never route empty)
|
|
78
|
+
if voice_present(inbound.media) and not (inbound.text or "").strip():
|
|
79
|
+
try:
|
|
80
|
+
_vtext = await transcribe_inbound_media(container, inbound.media)
|
|
81
|
+
except Exception:
|
|
82
|
+
_vtext = None
|
|
83
|
+
if _vtext:
|
|
84
|
+
inbound.text = _vtext
|
|
85
|
+
for _m in inbound.media:
|
|
86
|
+
if getattr(_m, "kind", None) in ("voice", "audio"):
|
|
87
|
+
_m.transcript = _vtext
|
|
88
|
+
break
|
|
89
|
+
if voice_needs_guard(inbound.media, inbound.text):
|
|
90
|
+
await self._send_immediate(
|
|
91
|
+
inbound,
|
|
92
|
+
voice_unavailable_message(SurfaceConfig.voice_transcription_enabled()),
|
|
93
|
+
)
|
|
94
|
+
continue
|
|
95
|
+
# Persistent transcript echo (voice only) — see core.surfaces.voice_echo.
|
|
96
|
+
# Lands before the agent runs; fail-open (never blocks the turn).
|
|
97
|
+
if SurfaceConfig.voice_transcript_echo_enabled():
|
|
98
|
+
_t = voice_transcript(inbound.media)
|
|
99
|
+
if _t:
|
|
100
|
+
try:
|
|
101
|
+
await self._send_immediate(
|
|
102
|
+
inbound, voice_echo_message(_t),
|
|
103
|
+
reply_to=inbound.idempotency_key)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.debug("%s transcript echo failed: %s", self.surface_id, e)
|
|
106
|
+
_mr = getattr(self, "mark_read_inbound", None)
|
|
107
|
+
if _mr is not None:
|
|
108
|
+
try:
|
|
109
|
+
await _mr(inbound)
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
decision = await route_inbound(container, inbound)
|
|
113
|
+
result = InboundResult(inbound=inbound, decision=decision)
|
|
114
|
+
reply = await act_on_inbound(task_agent, result)
|
|
115
|
+
if reply:
|
|
116
|
+
await self._send_immediate(inbound, reply)
|
|
117
|
+
except Exception as e: # fail-open per message
|
|
118
|
+
logger.error("%s webhook: process failed: %s", self.surface_id, e, exc_info=True)
|
|
119
|
+
return {"ok": True}
|
|
120
|
+
|
|
121
|
+
async def _send_immediate(self, inbound: InboundMessage, text: str, reply_to=None) -> None:
|
|
122
|
+
"""Deliver a synchronous reply (DENIED notice / command ack / transcript echo).
|
|
123
|
+
Override per surface. ``reply_to`` optionally quotes the inbound message."""
|
|
124
|
+
logger.debug("%s immediate reply suppressed (no transport): %s", self.surface_id, text[:80])
|
core/surfaces/media.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Typed media envelope shared by every surface. Inbound media (voice/image/doc) is
|
|
2
|
+
normalized to Media so the core transcription/handling seams are transport-agnostic.
|
|
3
|
+
Kept intentionally small: bytes XOR url; a surface fills whichever it has cheaply."""
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
_KINDS = {"voice", "audio", "image", "video", "document", "sticker"}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Media:
|
|
12
|
+
kind: str
|
|
13
|
+
mime: Optional[str] = None
|
|
14
|
+
data: Optional[bytes] = None
|
|
15
|
+
url: Optional[str] = None
|
|
16
|
+
caption: Optional[str] = None
|
|
17
|
+
filename: Optional[str] = None
|
|
18
|
+
transcript: Optional[str] = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def coerce_media(items: list) -> List[Media]:
|
|
22
|
+
out: List[Media] = []
|
|
23
|
+
for it in items or []:
|
|
24
|
+
if isinstance(it, Media):
|
|
25
|
+
out.append(it)
|
|
26
|
+
elif isinstance(it, dict) and it.get("kind") in _KINDS:
|
|
27
|
+
out.append(Media(
|
|
28
|
+
kind=it["kind"], mime=it.get("mime"), data=it.get("data"),
|
|
29
|
+
url=it.get("url"), caption=it.get("caption"), filename=it.get("filename"),
|
|
30
|
+
transcript=it.get("transcript"),
|
|
31
|
+
))
|
|
32
|
+
return out
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""MessageRouter: the SINGLE outbound producer seam.
|
|
2
|
+
|
|
3
|
+
Every agent->user emit funnels through publish(). Text is brain-scrubbed here
|
|
4
|
+
(SSOT = modules.llm.brain_scrubber) so every surface inherits the scrub. Surfaces
|
|
5
|
+
subscribe by surface_id; routing resolves session_key -> surface_id via the
|
|
6
|
+
SessionChatRegistry. Fail-open: an unroutable key or a raising surface never
|
|
7
|
+
crashes the agent loop.
|
|
8
|
+
"""
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
from core.surfaces.envelopes import OutboundMessage
|
|
12
|
+
from core.surfaces.session_chat_registry import SessionChatRegistry
|
|
13
|
+
from modules.llm.brain_scrubber import scrub_brain_blocks
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MessageRouter:
|
|
19
|
+
def __init__(self, registry: SessionChatRegistry) -> None:
|
|
20
|
+
self._registry = registry
|
|
21
|
+
self._surfaces: dict[str, object] = {}
|
|
22
|
+
self._queue = None
|
|
23
|
+
|
|
24
|
+
def attach_queue(self, q) -> None:
|
|
25
|
+
"""Attach a durable OutboundDeliveryQueue. Call from bootstrap after construction."""
|
|
26
|
+
self._queue = q
|
|
27
|
+
|
|
28
|
+
def subscribe(self, surface_id: str, surface) -> None:
|
|
29
|
+
self._surfaces[surface_id] = surface
|
|
30
|
+
|
|
31
|
+
async def publish(self, msg: OutboundMessage) -> None:
|
|
32
|
+
try:
|
|
33
|
+
scrubbed = scrub_brain_blocks(msg.text)
|
|
34
|
+
except Exception: # fail-open: never drop a reply over a scrub bug
|
|
35
|
+
scrubbed = msg.text
|
|
36
|
+
# F3 parity with HITLManager.stream_output: a wholly-brain (or empty) chunk
|
|
37
|
+
# scrubs to None/"" and must be DROPPED, not delivered as an empty bubble.
|
|
38
|
+
if scrubbed is None or not scrubbed.strip():
|
|
39
|
+
return
|
|
40
|
+
if scrubbed != msg.text:
|
|
41
|
+
msg = OutboundMessage(
|
|
42
|
+
session_key=msg.session_key, text=scrubbed, kind=msg.kind,
|
|
43
|
+
partial=msg.partial, stream_id=msg.stream_id,
|
|
44
|
+
reply_to=msg.reply_to, media=msg.media,
|
|
45
|
+
)
|
|
46
|
+
row = self._registry.resolve(msg.session_key)
|
|
47
|
+
if not row:
|
|
48
|
+
logger.debug("message_router: no binding for %s; dropping", msg.session_key)
|
|
49
|
+
return
|
|
50
|
+
surface = self._surfaces.get(row.get("surface_id"))
|
|
51
|
+
if surface is None:
|
|
52
|
+
logger.debug("message_router: no surface %s subscribed", row.get("surface_id"))
|
|
53
|
+
return
|
|
54
|
+
# Durable path (final messages only): enqueue instead of sending directly.
|
|
55
|
+
from agents.task.surface_config import SurfaceConfig
|
|
56
|
+
if (self._queue is not None and not msg.partial
|
|
57
|
+
and SurfaceConfig.outbound_queue_enabled()):
|
|
58
|
+
turn = msg.stream_id or msg.session_key
|
|
59
|
+
idem = f"{msg.session_key}#{turn}#{hash(scrubbed) & 0xffffffff}"
|
|
60
|
+
try:
|
|
61
|
+
self._queue.enqueue(
|
|
62
|
+
idempotency_key=idem, session_key=msg.session_key,
|
|
63
|
+
surface_id=row.get("surface_id"), dest=row.get("chat_id"),
|
|
64
|
+
payload=scrubbed, kind=str(getattr(msg.kind, "value", msg.kind)),
|
|
65
|
+
)
|
|
66
|
+
except Exception as e: # fail-open: fall back to a direct send on a queue fault
|
|
67
|
+
logger.error("outbound enqueue failed, sending directly: %s", e)
|
|
68
|
+
else:
|
|
69
|
+
return
|
|
70
|
+
try:
|
|
71
|
+
if msg.partial:
|
|
72
|
+
await surface.stream(msg) # base buffers if surface can't stream
|
|
73
|
+
else:
|
|
74
|
+
await surface.send(msg)
|
|
75
|
+
except Exception as e: # fail-open
|
|
76
|
+
logger.error("message_router: surface %s raised: %s", row.get("surface_id"), e, exc_info=True)
|
|
77
|
+
|
|
78
|
+
async def send_message(self, chat_id: str, text: str, surface_id: str = "telegram") -> bool:
|
|
79
|
+
"""Back-compat shim for cron/delivery.py. Returns True only on a completed send."""
|
|
80
|
+
surface = self._surfaces.get(surface_id)
|
|
81
|
+
if surface is None:
|
|
82
|
+
logger.warning("send_message: no surface %s registered — delivery failed", surface_id)
|
|
83
|
+
return False
|
|
84
|
+
try:
|
|
85
|
+
await surface.send(OutboundMessage(
|
|
86
|
+
session_key=f"direct:{surface_id}:{chat_id}", text=text,
|
|
87
|
+
))
|
|
88
|
+
return True
|
|
89
|
+
except Exception as e:
|
|
90
|
+
logger.error("send_message shim failed: %s", e, exc_info=True)
|
|
91
|
+
return False
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Owner-scoped outbound send-authorization. Default-DENY: a (user_id, surface,
|
|
2
|
+
target) must have an ACTIVE row to be sendable. Owner targets bypass this (resolved
|
|
3
|
+
upstream in outbound_target.resolve_target_tier). Tenant-scoped, WAL+jitter via
|
|
4
|
+
``core/sqlite_util`` (no hand-rolled retry loop) — mirrors ``agents/task/goals/board.py``.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import time
|
|
10
|
+
from typing import Dict, List
|
|
11
|
+
|
|
12
|
+
from core.sqlite_util import execute_retry
|
|
13
|
+
|
|
14
|
+
_DDL = """
|
|
15
|
+
CREATE TABLE IF NOT EXISTS outbound_allowlist (
|
|
16
|
+
user_id TEXT NOT NULL,
|
|
17
|
+
surface TEXT NOT NULL,
|
|
18
|
+
target TEXT NOT NULL,
|
|
19
|
+
note TEXT NOT NULL DEFAULT '',
|
|
20
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
21
|
+
created_at REAL NOT NULL,
|
|
22
|
+
PRIMARY KEY (user_id, surface, target)
|
|
23
|
+
);
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class OutboundAllowlist:
|
|
28
|
+
def __init__(self, db_path: str) -> None:
|
|
29
|
+
self.db_path = db_path
|
|
30
|
+
parent = os.path.dirname(db_path)
|
|
31
|
+
if parent:
|
|
32
|
+
os.makedirs(parent, exist_ok=True)
|
|
33
|
+
execute_retry(self.db_path, _DDL)
|
|
34
|
+
|
|
35
|
+
def allow(self, user_id: str, surface: str, target: str, note: str = "") -> None:
|
|
36
|
+
execute_retry(
|
|
37
|
+
self.db_path,
|
|
38
|
+
"INSERT INTO outbound_allowlist(user_id,surface,target,note,status,created_at)"
|
|
39
|
+
" VALUES(?,?,?,?, 'active', ?)"
|
|
40
|
+
" ON CONFLICT(user_id,surface,target) DO UPDATE SET status='active', note=excluded.note",
|
|
41
|
+
(user_id, surface, target, note, time.time()),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def revoke(self, user_id: str, surface: str, target: str) -> bool:
|
|
45
|
+
rc = execute_retry(
|
|
46
|
+
self.db_path,
|
|
47
|
+
"UPDATE outbound_allowlist SET status='revoked'"
|
|
48
|
+
" WHERE user_id=? AND surface=? AND target=? AND status='active'",
|
|
49
|
+
(user_id, surface, target),
|
|
50
|
+
)
|
|
51
|
+
return bool(rc)
|
|
52
|
+
|
|
53
|
+
def is_allowed(self, user_id: str, surface: str, target: str) -> bool:
|
|
54
|
+
row = execute_retry(
|
|
55
|
+
self.db_path,
|
|
56
|
+
"SELECT 1 FROM outbound_allowlist"
|
|
57
|
+
" WHERE user_id=? AND surface=? AND target=? AND status='active'",
|
|
58
|
+
(user_id, surface, target),
|
|
59
|
+
fetch="one",
|
|
60
|
+
)
|
|
61
|
+
return row is not None
|
|
62
|
+
|
|
63
|
+
def list(self, user_id: str) -> List[Dict]:
|
|
64
|
+
rows = execute_retry(
|
|
65
|
+
self.db_path,
|
|
66
|
+
"SELECT surface,target,note,status,created_at FROM outbound_allowlist"
|
|
67
|
+
" WHERE user_id=? ORDER BY created_at DESC",
|
|
68
|
+
(user_id,),
|
|
69
|
+
fetch="all",
|
|
70
|
+
) or []
|
|
71
|
+
return [
|
|
72
|
+
{
|
|
73
|
+
"surface": r["surface"],
|
|
74
|
+
"target": r["target"],
|
|
75
|
+
"note": r["note"],
|
|
76
|
+
"status": r["status"],
|
|
77
|
+
"created_at": r["created_at"],
|
|
78
|
+
}
|
|
79
|
+
for r in rows
|
|
80
|
+
]
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Drains OutboundDeliveryQueue: per-dest token-bucket pace, send via the surface, backoff
|
|
2
|
+
on failure, dead-letter after max_attempts. Deterministic backoff base (jitter added only
|
|
3
|
+
in the live run loop, not drain_once, so tests are reproducible)."""
|
|
4
|
+
import asyncio
|
|
5
|
+
import logging
|
|
6
|
+
import random
|
|
7
|
+
from typing import Callable, Optional
|
|
8
|
+
|
|
9
|
+
from core.surfaces.envelopes import OutboundMessage
|
|
10
|
+
from core.surfaces.outbound_queue import OutboundDeliveryQueue
|
|
11
|
+
from core.surfaces.rate_bucket import TokenBucket
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
# TYPE_CHECKING import avoids a circular-import risk; the breaker is pure.
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from core.surfaces.circuit import SurfaceCircuitBreaker
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class OutboundDispatcher:
|
|
22
|
+
def __init__(self, queue: OutboundDeliveryQueue,
|
|
23
|
+
surface_lookup: Callable[[str], Optional[object]], *,
|
|
24
|
+
max_attempts: int = 6, base_backoff: float = 2.0,
|
|
25
|
+
rate_per_sec: float = 20.0, burst: int = 20,
|
|
26
|
+
circuit: Optional["SurfaceCircuitBreaker"] = None) -> None:
|
|
27
|
+
self._q = queue
|
|
28
|
+
self._lookup = surface_lookup
|
|
29
|
+
self._max = max_attempts
|
|
30
|
+
self._base = base_backoff
|
|
31
|
+
self._bucket = TokenBucket(rate_per_sec, burst)
|
|
32
|
+
self._cb = circuit # SurfaceCircuitBreaker | None
|
|
33
|
+
self._task: Optional[asyncio.Task] = None
|
|
34
|
+
self._stop = asyncio.Event()
|
|
35
|
+
|
|
36
|
+
async def drain_once(self, now: float) -> int:
|
|
37
|
+
delivered = 0
|
|
38
|
+
for row in self._q.claim_due(now):
|
|
39
|
+
surface_id = row["surface_id"]
|
|
40
|
+
|
|
41
|
+
# --- Circuit breaker: skip open surfaces; defer 30 s, attempts unchanged ---
|
|
42
|
+
if self._cb is not None and self._cb.is_open(surface_id):
|
|
43
|
+
self._q.reschedule(row["id"], next_attempt_at=now + 30,
|
|
44
|
+
attempts=row["attempts"])
|
|
45
|
+
logger.debug("outbound circuit OPEN: surface=%s deferred 30s", surface_id)
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
key = f"{surface_id}:{row['dest']}"
|
|
49
|
+
allowed, retry_after = self._bucket.take(key, now=now)
|
|
50
|
+
if not allowed:
|
|
51
|
+
self._q.reschedule(row["id"], next_attempt_at=now + retry_after,
|
|
52
|
+
attempts=row["attempts"])
|
|
53
|
+
continue
|
|
54
|
+
surface = self._lookup(surface_id)
|
|
55
|
+
ok, err = False, "no surface"
|
|
56
|
+
if surface is not None:
|
|
57
|
+
try:
|
|
58
|
+
res = await surface.send(OutboundMessage(
|
|
59
|
+
session_key=row["session_key"], text=row["payload"],
|
|
60
|
+
))
|
|
61
|
+
ok = bool(getattr(res, "success", False))
|
|
62
|
+
err = getattr(res, "error", None) or ("ok" if ok else "send returned False")
|
|
63
|
+
except Exception as exc: # fail-open: a raising surface reschedules, never crashes
|
|
64
|
+
ok, err = False, str(exc)
|
|
65
|
+
if ok:
|
|
66
|
+
if self._cb is not None:
|
|
67
|
+
self._cb.record_ok(surface_id)
|
|
68
|
+
self._q.mark_delivered(row["id"])
|
|
69
|
+
delivered += 1
|
|
70
|
+
else:
|
|
71
|
+
if self._cb is not None:
|
|
72
|
+
self._cb.record_fail(surface_id)
|
|
73
|
+
attempts = row["attempts"] + 1
|
|
74
|
+
if attempts >= self._max:
|
|
75
|
+
self._q.dead_letter(row["id"], err)
|
|
76
|
+
logger.error("outbound DEAD-LETTER id=%s surface=%s dest=%s err=%s",
|
|
77
|
+
row["id"], surface_id, row["dest"], err)
|
|
78
|
+
else:
|
|
79
|
+
backoff = self._base * (2 ** (attempts - 1))
|
|
80
|
+
self._q.reschedule(row["id"], next_attempt_at=now + backoff,
|
|
81
|
+
attempts=attempts, error=err)
|
|
82
|
+
return delivered
|
|
83
|
+
|
|
84
|
+
async def run(self, interval: float = 1.0) -> None:
|
|
85
|
+
import time as _t
|
|
86
|
+
while not self._stop.is_set():
|
|
87
|
+
try:
|
|
88
|
+
now_ts = _t.time()
|
|
89
|
+
if now_ts - getattr(self, "_last_reclaim", 0) > 60:
|
|
90
|
+
self._q.reclaim_inflight(older_than=now_ts - 120)
|
|
91
|
+
self._last_reclaim = now_ts
|
|
92
|
+
await self.drain_once(now=now_ts + random.uniform(0, 0.05))
|
|
93
|
+
except Exception as exc:
|
|
94
|
+
logger.error("outbound dispatcher loop error: %s", exc, exc_info=True)
|
|
95
|
+
try:
|
|
96
|
+
await asyncio.wait_for(self._stop.wait(), timeout=interval)
|
|
97
|
+
except asyncio.TimeoutError:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
def start(self) -> None:
|
|
101
|
+
if self._task is None:
|
|
102
|
+
self._task = asyncio.ensure_future(self.run())
|
|
103
|
+
|
|
104
|
+
async def stop(self) -> None:
|
|
105
|
+
self._stop.set()
|
|
106
|
+
if self._task is not None:
|
|
107
|
+
try:
|
|
108
|
+
await asyncio.wait_for(self._task, timeout=5.0)
|
|
109
|
+
except Exception:
|
|
110
|
+
self._task.cancel()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Outbound-collapse mirror helpers (P1a).
|
|
2
|
+
|
|
3
|
+
Small factories that mirror an agent's discrete user-facing message into the
|
|
4
|
+
unified MessageRouter seam as a committed (partial=False) OutboundMessage. Additive
|
|
5
|
+
and gated on SINGULAR_CHAT_ENABLED; a no-op (and fail-open) when the flag is OFF or
|
|
6
|
+
no router / session_key has been bound, so the legacy add_to_feed path stays
|
|
7
|
+
byte-identical until later phases wire a router.
|
|
8
|
+
"""
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, Awaitable, Callable, Optional
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def build_discrete_publish(
|
|
16
|
+
router: Any, session_key: Optional[str]
|
|
17
|
+
) -> Callable[..., Awaitable[None]]:
|
|
18
|
+
async def _publish(text: str) -> None:
|
|
19
|
+
from agents.task.surface_config import SurfaceConfig
|
|
20
|
+
|
|
21
|
+
if not SurfaceConfig.singular_chat_enabled():
|
|
22
|
+
return
|
|
23
|
+
if router is None or not session_key:
|
|
24
|
+
return
|
|
25
|
+
try:
|
|
26
|
+
from core.surfaces.envelopes import OutboundMessage, MessageKind
|
|
27
|
+
|
|
28
|
+
await router.publish(OutboundMessage(
|
|
29
|
+
session_key=session_key,
|
|
30
|
+
text=text,
|
|
31
|
+
kind=MessageKind.AGENT_TEXT,
|
|
32
|
+
partial=False,
|
|
33
|
+
))
|
|
34
|
+
except Exception as e: # fail-open: outbound mirror is non-critical
|
|
35
|
+
logger.debug("discrete publish mirror failed: %s", e)
|
|
36
|
+
|
|
37
|
+
return _publish
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Durable outbound delivery queue (SQLite WAL). Converts MessageRouter's fire-and-forget
|
|
2
|
+
send into at-least-once-with-dedup: publish() enqueues; a dispatcher worker drains with
|
|
3
|
+
backoff + token-bucket, dead-lettering after N attempts. idempotency_key (hash of
|
|
4
|
+
session_key+turn_id+chunk_idx) dedups a redelivery after a worker crash."""
|
|
5
|
+
import logging
|
|
6
|
+
import time as _time
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
from core.sqlite_util import wal_connect, execute_retry
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
_SCHEMA = """
|
|
14
|
+
CREATE TABLE IF NOT EXISTS outbound_queue (
|
|
15
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
16
|
+
idempotency_key TEXT UNIQUE,
|
|
17
|
+
session_key TEXT NOT NULL,
|
|
18
|
+
surface_id TEXT NOT NULL,
|
|
19
|
+
dest TEXT,
|
|
20
|
+
payload TEXT NOT NULL,
|
|
21
|
+
kind TEXT DEFAULT 'agent_text',
|
|
22
|
+
state TEXT DEFAULT 'pending', -- pending|inflight|delivered|dead
|
|
23
|
+
attempts INTEGER DEFAULT 0,
|
|
24
|
+
next_attempt_at REAL DEFAULT 0,
|
|
25
|
+
last_error TEXT,
|
|
26
|
+
created_at REAL DEFAULT (strftime('%s','now')),
|
|
27
|
+
updated_at REAL DEFAULT (strftime('%s','now'))
|
|
28
|
+
)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class OutboundDeliveryQueue:
|
|
33
|
+
def __init__(self, db_path: str) -> None:
|
|
34
|
+
self.db_path = db_path
|
|
35
|
+
conn = wal_connect(db_path)
|
|
36
|
+
try:
|
|
37
|
+
conn.execute(_SCHEMA)
|
|
38
|
+
conn.commit()
|
|
39
|
+
finally:
|
|
40
|
+
conn.close()
|
|
41
|
+
|
|
42
|
+
def enqueue(self, *, idempotency_key: str, session_key: str, surface_id: str,
|
|
43
|
+
dest: Optional[str], payload: str, kind: str = "agent_text") -> bool:
|
|
44
|
+
inserted = execute_retry(
|
|
45
|
+
self.db_path,
|
|
46
|
+
"""INSERT OR IGNORE INTO outbound_queue
|
|
47
|
+
(idempotency_key, session_key, surface_id, dest, payload, kind, next_attempt_at)
|
|
48
|
+
VALUES (?, ?, ?, ?, ?, ?, 0)""",
|
|
49
|
+
(idempotency_key, session_key, surface_id, dest, payload, kind),
|
|
50
|
+
)
|
|
51
|
+
return inserted == 1
|
|
52
|
+
|
|
53
|
+
def claim_due(self, now: float, limit: int = 20) -> List[dict]:
|
|
54
|
+
# Two-step claim under WAL: select due ids, then CAS each to 'inflight'.
|
|
55
|
+
rows = execute_retry(
|
|
56
|
+
self.db_path,
|
|
57
|
+
"""SELECT * FROM outbound_queue
|
|
58
|
+
WHERE state='pending' AND next_attempt_at <= ?
|
|
59
|
+
ORDER BY id ASC LIMIT ?""",
|
|
60
|
+
(now, limit), fetch="all",
|
|
61
|
+
) or []
|
|
62
|
+
claimed = []
|
|
63
|
+
for r in rows:
|
|
64
|
+
n = execute_retry(
|
|
65
|
+
self.db_path,
|
|
66
|
+
"UPDATE outbound_queue SET state='inflight', updated_at=? WHERE id=? AND state='pending'",
|
|
67
|
+
(now, r["id"]),
|
|
68
|
+
)
|
|
69
|
+
if n == 1:
|
|
70
|
+
d = dict(r); d["state"] = "inflight"; claimed.append(d)
|
|
71
|
+
return claimed
|
|
72
|
+
|
|
73
|
+
def mark_delivered(self, row_id: int) -> None:
|
|
74
|
+
execute_retry(self.db_path,
|
|
75
|
+
"UPDATE outbound_queue SET state='delivered', updated_at=? WHERE id=?",
|
|
76
|
+
(_time.time(), row_id))
|
|
77
|
+
|
|
78
|
+
def reschedule(self, row_id: int, *, next_attempt_at: float, attempts: int,
|
|
79
|
+
error: Optional[str] = None) -> None:
|
|
80
|
+
execute_retry(
|
|
81
|
+
self.db_path,
|
|
82
|
+
"""UPDATE outbound_queue SET state='pending', attempts=?, next_attempt_at=?,
|
|
83
|
+
last_error=?, updated_at=? WHERE id=?""",
|
|
84
|
+
(attempts, next_attempt_at, error, _time.time(), row_id),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def dead_letter(self, row_id: int, error: str) -> None:
|
|
88
|
+
execute_retry(self.db_path,
|
|
89
|
+
"UPDATE outbound_queue SET state='dead', last_error=?, updated_at=? WHERE id=?",
|
|
90
|
+
(error, _time.time(), row_id))
|
|
91
|
+
|
|
92
|
+
def counts(self) -> dict:
|
|
93
|
+
rows = execute_retry(self.db_path,
|
|
94
|
+
"SELECT state, COUNT(*) c FROM outbound_queue GROUP BY state",
|
|
95
|
+
fetch="all") or []
|
|
96
|
+
out = {"pending": 0, "inflight": 0, "delivered": 0, "dead": 0}
|
|
97
|
+
for r in rows:
|
|
98
|
+
out[r["state"]] = r["c"]
|
|
99
|
+
return out
|
|
100
|
+
|
|
101
|
+
def reclaim_inflight(self, older_than: float) -> int:
|
|
102
|
+
"""Restart-recovery: return long-inflight rows to 'pending' (a worker died mid-send)."""
|
|
103
|
+
return execute_retry(
|
|
104
|
+
self.db_path,
|
|
105
|
+
"UPDATE outbound_queue SET state='pending' WHERE state='inflight' AND updated_at < ?",
|
|
106
|
+
(older_than,),
|
|
107
|
+
) or 0
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Pure resolver: is this outbound target the owner, an allowlisted party, or denied?
|
|
2
|
+
Owner-address map is injected (from core/instance resolvers at the call site) so this
|
|
3
|
+
stays pure and unit-testable."""
|
|
4
|
+
|
|
5
|
+
def resolve_target_tier(*, surface: str, target: str, user_id: str, allowlist,
|
|
6
|
+
owner_targets: dict) -> str:
|
|
7
|
+
owner_addr = (owner_targets or {}).get(surface)
|
|
8
|
+
if owner_addr is not None and str(target) == str(owner_addr):
|
|
9
|
+
return "owner"
|
|
10
|
+
if allowlist is not None and allowlist.is_allowed(user_id, surface, target):
|
|
11
|
+
return "allowlisted"
|
|
12
|
+
return "denied"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""WS-D: owner/access quick-access — one read of the access SSOT.
|
|
2
|
+
|
|
3
|
+
A single place that reports who the agent serves and the per-surface access posture, so
|
|
4
|
+
the `polyrob owner` CLI (and any admin surface) doesn't re-derive it from scattered
|
|
5
|
+
flags. Owner-by-email is reported as a hard OFF in v1 (a forgeable From: can never
|
|
6
|
+
confer OWNER tier until verified-sender lands).
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Dict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def owner_access_summary() -> Dict[str, Any]:
|
|
14
|
+
from core.instance import resolve_owner_principal
|
|
15
|
+
from agents.task.surface_config import SurfaceConfig
|
|
16
|
+
return {
|
|
17
|
+
# STRICT: report only an EXPLICITLY-bound owner (None = running on the
|
|
18
|
+
# auto-derived instance-id default), so the summary reflects real config.
|
|
19
|
+
"owner_principal": resolve_owner_principal(default_to_instance=False),
|
|
20
|
+
"correspondent_access_enabled": SurfaceConfig.correspondent_access_enabled(),
|
|
21
|
+
"require_approval": SurfaceConfig.correspondent_require_approval(),
|
|
22
|
+
"max_new_correspondents_per_day": SurfaceConfig.correspondent_max_new_per_day(),
|
|
23
|
+
"surfaces": {
|
|
24
|
+
"telegram": SurfaceConfig.telegram_surface_enabled(),
|
|
25
|
+
"whatsapp": SurfaceConfig.whatsapp_surface_enabled(),
|
|
26
|
+
"email": SurfaceConfig.email_surface_enabled(),
|
|
27
|
+
},
|
|
28
|
+
"owner_by_email": False, # v1: hard OFF (forgeable From:)
|
|
29
|
+
}
|