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
core/wallet/signer.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Signer seam: produces ECDSA / EIP-712 signatures for ONE agent address.
|
|
2
|
+
|
|
3
|
+
The raw private key NEVER crosses this boundary outward. LocalEoaSigner is the
|
|
4
|
+
self-custody backend; CdpSigner/TurnkeySigner (deferred) implement the same
|
|
5
|
+
Protocol so the custody backend is swappable without touching venue tools.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Protocol, runtime_checkable
|
|
10
|
+
|
|
11
|
+
from eth_account import Account
|
|
12
|
+
from eth_account.messages import encode_defunct, encode_typed_data
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@runtime_checkable
|
|
16
|
+
class Signer(Protocol):
|
|
17
|
+
@property
|
|
18
|
+
def address(self) -> str: ...
|
|
19
|
+
def sign_message(self, data: bytes) -> str: ...
|
|
20
|
+
def sign_typed_data(self, domain: dict, types: dict, message: dict) -> str: ...
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class LocalEoaSigner:
|
|
24
|
+
"""Self-custody EOA signer wrapping an eth_account LocalAccount."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, private_key: bytes):
|
|
27
|
+
self._account = Account.from_key(private_key)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def address(self) -> str:
|
|
31
|
+
return self._account.address
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def account(self):
|
|
35
|
+
"""The native LocalAccount, for SDKs that require it (e.g. Hyperliquid).
|
|
36
|
+
|
|
37
|
+
In-process use only — never returned from a tool action / logged."""
|
|
38
|
+
return self._account
|
|
39
|
+
|
|
40
|
+
def sign_message(self, data: bytes) -> str:
|
|
41
|
+
signed = self._account.sign_message(encode_defunct(data))
|
|
42
|
+
return signed.signature.hex() if signed.signature.hex().startswith("0x") else "0x" + signed.signature.hex()
|
|
43
|
+
|
|
44
|
+
def sign_typed_data(self, domain: dict, types: dict, message: dict) -> str:
|
|
45
|
+
signable = encode_typed_data(domain_data=domain, message_types=types, message_data=message)
|
|
46
|
+
signed = self._account.sign_message(signable)
|
|
47
|
+
sig = signed.signature.hex()
|
|
48
|
+
return sig if sig.startswith("0x") else "0x" + sig
|
|
49
|
+
|
|
50
|
+
def __repr__(self) -> str: # never leak the key
|
|
51
|
+
return f"<LocalEoaSigner address={self._account.address}>"
|
cron/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Cron Package — durable scheduled runs
|
|
2
|
+
|
|
3
|
+
_Last reviewed: 2026-06-30. For env flags see ../docs/CONFIGURATION.md; for the autonomy loops see ../docs/guide/architecture.md and ../AGENTS.md._
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `cron` package provides **durable, scheduled agent runs** — the home for work
|
|
8
|
+
that must survive a process restart, which in-memory `delegate_task` cannot. Jobs
|
|
9
|
+
are persisted in SQLite (`data/cron.db`, WAL + jittered retry) and executed by a
|
|
10
|
+
ticker that is safe under multiple workers via a file-based tick lock.
|
|
11
|
+
|
|
12
|
+
Off by default: nothing ticks unless `CRON_ENABLED=true`. It is wired into both the
|
|
13
|
+
API lifespan and the local CLI through the shared autonomy runtime
|
|
14
|
+
(`core/autonomy_runtime.py::start_autonomy`), alongside the goal board and curator.
|
|
15
|
+
|
|
16
|
+
## Package structure
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
cron/
|
|
20
|
+
├── schedule.py # PURE schedule parser + next-run computation (tz-naive, local
|
|
21
|
+
│ # wall-clock, reference time passed in → deterministic):
|
|
22
|
+
│ # durations ("30m"), `every monday 09:00`, 5-field cron,
|
|
23
|
+
│ # ISO one-shot
|
|
24
|
+
├── jobs.py # SQLite cron_jobs store — durable schedule state; WAL mode with
|
|
25
|
+
│ # jittered retry on write contention
|
|
26
|
+
├── service.py # CronService — schedule / list / cancel API over schedule+jobs
|
|
27
|
+
│ # (thin, fully testable; shared by the tool and the ticker)
|
|
28
|
+
├── scheduler.py # tick(): find due jobs → run each through an injected async
|
|
29
|
+
│ # runner under a hard per-run duration cap → reschedule
|
|
30
|
+
│ # (recurring) or complete (one-shot); file-based TickLock
|
|
31
|
+
├── runner.py # CronTicker (drives tick() on an interval) + make_agent_runner
|
|
32
|
+
│ # (W3 fix: actually runs run_session, not just create_session)
|
|
33
|
+
└── delivery.py # out-of-band result delivery after a run: allowlist
|
|
34
|
+
# {telegram, email, twitter}; [SILENT] suppresses; tenant-scoped
|
|
35
|
+
# recipient; fail-open; inside the scheduler wait_for budget
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Execution model
|
|
39
|
+
|
|
40
|
+
1. `CronTicker` calls `scheduler.tick()` on an interval.
|
|
41
|
+
2. `tick()` acquires the file `TickLock` (safe under `workers>1`), finds due jobs,
|
|
42
|
+
and runs each through the injected runner with a hard duration cap.
|
|
43
|
+
3. `make_agent_runner` runs the real agent loop (`create_session` + `run_session`,
|
|
44
|
+
`skip_memory=True`); a one-shot job is marked done, a recurring job is
|
|
45
|
+
rescheduled to its next run.
|
|
46
|
+
4. If the job requested delivery, `delivery.py` sends the final result to the
|
|
47
|
+
chosen sink (within the tick's `wait_for` budget).
|
|
48
|
+
|
|
49
|
+
## Key invariants
|
|
50
|
+
|
|
51
|
+
- **W3 run-loop fix (default on, `CRON_RUN_LOOP`):** cron historically called only
|
|
52
|
+
`create_session` and never ran the loop, so jobs created an idle session and did
|
|
53
|
+
nothing. It now runs `run_session`.
|
|
54
|
+
- **Idle-gate:** `scheduler.tick()` skips while the interactive REPL is busy
|
|
55
|
+
(shared CWD safety — see `core/interactive_gate.py`); inert on the server.
|
|
56
|
+
- **Delivery is gated** separately (`CRON_DELIVERY_ENABLED`, default off) and
|
|
57
|
+
fail-open.
|
|
58
|
+
|
|
59
|
+
## Related
|
|
60
|
+
|
|
61
|
+
- Agent-facing tool surface: `tools/cronjob_tools.py`
|
|
62
|
+
(`cronjob_schedule` / `list` / `cancel`), registered only when `CRON_ENABLED=true`.
|
|
63
|
+
- Durable cross-session **goal board** (reuses this package's `TickLock` pattern):
|
|
64
|
+
`agents/task/goals/` + `agents/task/goals/dispatcher.py`.
|
|
65
|
+
- Shared lifecycle: `core/autonomy_runtime.py`.
|
cron/__init__.py
ADDED
|
File without changes
|
cron/delivery.py
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""Cron result delivery (W3, Reference-parity §_deliver_result).
|
|
2
|
+
|
|
3
|
+
After a cron job runs the agent loop (W3 run-loop fix), its final result can be
|
|
4
|
+
delivered out-of-band to an external sink — email, twitter, or telegram — so a
|
|
5
|
+
scheduled task can actually *report back* instead of dying silently in a session.
|
|
6
|
+
|
|
7
|
+
Design constraints (Fusion-validated):
|
|
8
|
+
|
|
9
|
+
- **Allowlisted targets** ``{telegram, email, twitter}`` — never an arbitrary sink.
|
|
10
|
+
- **`[SILENT]`** anywhere in the result (case-insensitive) suppresses delivery,
|
|
11
|
+
matching the Reference convention (the agent opts a run out of notifying).
|
|
12
|
+
- **Tenant-scoped** — the recipient is resolved from the *job's owner*, not from a
|
|
13
|
+
free-form payload address, except where the agent is delivering to its own
|
|
14
|
+
configured channel. A cron job must not be able to exfiltrate to a stranger.
|
|
15
|
+
- **Fail-open** — a sink that errors (or is unconfigured) logs and returns False;
|
|
16
|
+
it NEVER fails the cron job. Delivery is best-effort reporting, not core work.
|
|
17
|
+
- **Inside the budget** — callers invoke this from within the scheduler's
|
|
18
|
+
``asyncio.wait_for(runner(job), timeout=max_duration_seconds)``, so delivery I/O
|
|
19
|
+
shares the per-run hard cap rather than running unbounded after a hard-cancel.
|
|
20
|
+
|
|
21
|
+
Gated by ``CRON_DELIVERY_ENABLED`` (default OFF) at the call site (``cron/runner.py``).
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import logging
|
|
26
|
+
from typing import Any, Optional
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
ALLOWED_TARGETS = ("telegram", "email", "twitter")
|
|
31
|
+
SILENT_MARKER = "[SILENT]"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _allow_explicit_target() -> bool:
|
|
35
|
+
"""Whether an agent-supplied ``deliver_target`` (arbitrary address/chat) is honored.
|
|
36
|
+
|
|
37
|
+
Default FALSE → delivery always goes to the JOB OWNER's own channel, closing the
|
|
38
|
+
exfiltration vector where a (possibly prompt-injected) cron job sets
|
|
39
|
+
``deliver_target="attacker@evil.com"``. Operators of trusted single-user installs
|
|
40
|
+
can opt in with ``CRON_DELIVERY_ALLOW_EXPLICIT_TARGET=true``.
|
|
41
|
+
"""
|
|
42
|
+
import os
|
|
43
|
+
return os.getenv("CRON_DELIVERY_ALLOW_EXPLICIT_TARGET", "").strip().lower() in (
|
|
44
|
+
"1", "true", "yes", "on")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def is_silent(final: Optional[str]) -> bool:
|
|
48
|
+
"""True when the agent opted this run out of delivery via ``[SILENT]``."""
|
|
49
|
+
return bool(final) and SILENT_MARKER in final.upper()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def delivery_outcome(final: Optional[str], ok: bool) -> str:
|
|
53
|
+
"""Classify a cron delivery for the observability log: ``suppressed`` (agent chose
|
|
54
|
+
``[SILENT]``), ``sent`` (delivered), or ``failed`` (real send failure).
|
|
55
|
+
|
|
56
|
+
The runner previously logged both a ``[SILENT]`` opt-out and a genuine send error
|
|
57
|
+
as ``ok=False``, so harmless status-digest opt-outs were indistinguishable from
|
|
58
|
+
broken deliveries in the journal. ``is_silent`` takes precedence because a silent
|
|
59
|
+
run never attempts a send (``deliver_result`` returns False for it).
|
|
60
|
+
"""
|
|
61
|
+
if is_silent(final):
|
|
62
|
+
return "suppressed"
|
|
63
|
+
return "sent" if ok else "failed"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def deliver_result(
|
|
67
|
+
task_agent: Any,
|
|
68
|
+
job: Any,
|
|
69
|
+
final: Optional[str],
|
|
70
|
+
*,
|
|
71
|
+
target: Optional[str],
|
|
72
|
+
deliver_target: Optional[str] = None,
|
|
73
|
+
session_id: Optional[str] = None,
|
|
74
|
+
) -> bool:
|
|
75
|
+
"""Deliver a cron job's final result to an allowlisted external sink.
|
|
76
|
+
|
|
77
|
+
Returns True on a successful send, False otherwise (unknown/blank target,
|
|
78
|
+
empty/`[SILENT]` result, sink unconfigured, or any sink exception). Never
|
|
79
|
+
raises — delivery must not fail the job.
|
|
80
|
+
|
|
81
|
+
``session_id`` (Task 7, optional): when provided, a SUCCESSFUL send marks the
|
|
82
|
+
episodic row for this session as ``surfaced`` (the digest builder then omits
|
|
83
|
+
it via ``exclude_surfaced=True``, avoiding double-reporting what was already
|
|
84
|
+
delivered out-of-band). Callers must pass ``session_id`` AFTER the episode
|
|
85
|
+
row has been written (``finalize_episode``) — the mark is a plain ``UPDATE``
|
|
86
|
+
that no-ops if the row doesn't exist yet (see ``cron/runner.py`` ordering).
|
|
87
|
+
"""
|
|
88
|
+
if not target:
|
|
89
|
+
return False
|
|
90
|
+
target = target.strip().lower()
|
|
91
|
+
if target not in ALLOWED_TARGETS:
|
|
92
|
+
logger.warning("cron delivery: target %r not in allowlist %s", target, ALLOWED_TARGETS)
|
|
93
|
+
return False
|
|
94
|
+
if not final or not final.strip():
|
|
95
|
+
return False
|
|
96
|
+
if is_silent(final):
|
|
97
|
+
logger.info("cron delivery: job %s opted out via [SILENT]", getattr(job, "id", "?"))
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
# Security: ignore an agent-supplied explicit recipient unless an operator opted in.
|
|
101
|
+
# By default, deliver only to the job owner's own channel (no exfiltration).
|
|
102
|
+
if deliver_target and not _allow_explicit_target():
|
|
103
|
+
logger.info("cron delivery: ignoring explicit deliver_target (owner-only by default)")
|
|
104
|
+
deliver_target = None
|
|
105
|
+
|
|
106
|
+
ok = False
|
|
107
|
+
try:
|
|
108
|
+
if target == "email":
|
|
109
|
+
ok = await _deliver_email(task_agent, job, final, deliver_target)
|
|
110
|
+
elif target == "twitter":
|
|
111
|
+
ok = await _deliver_twitter(task_agent, job, final)
|
|
112
|
+
elif target == "telegram":
|
|
113
|
+
ok = await _deliver_telegram(task_agent, job, final, deliver_target)
|
|
114
|
+
except Exception as e: # fail-open: a delivery error never fails the job
|
|
115
|
+
logger.error("cron delivery to %s failed for job %s: %s",
|
|
116
|
+
target, getattr(job, "id", "?"), e, exc_info=True)
|
|
117
|
+
return False
|
|
118
|
+
if ok and session_id:
|
|
119
|
+
_mark_surfaced(session_id, getattr(job, "user_id", None))
|
|
120
|
+
return ok
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _mark_surfaced(session_id: str, user_id: Optional[str] = None) -> None:
|
|
124
|
+
"""Fail-open: mark the delivered session's episode as surfaced (Task 7).
|
|
125
|
+
|
|
126
|
+
Scoped to the job's own ``user_id`` (episodes are keyed on the composite
|
|
127
|
+
``(user_id, session_id)``) so a session_id collision across tenants can't
|
|
128
|
+
flip another tenant's row.
|
|
129
|
+
"""
|
|
130
|
+
try:
|
|
131
|
+
from modules.memory.registry import get_memory_registry
|
|
132
|
+
prov = get_memory_registry().active()
|
|
133
|
+
if prov is not None and hasattr(prov, "mark_episode_surfaced"):
|
|
134
|
+
prov.mark_episode_surfaced(session_id=session_id, user_id=user_id)
|
|
135
|
+
except Exception:
|
|
136
|
+
logger.debug("cron delivery: mark_episode_surfaced skipped", exc_info=True)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# --- sinks -------------------------------------------------------------------
|
|
140
|
+
# Each sink resolves its tool lazily from the task_agent's container/config and is
|
|
141
|
+
# wrapped by deliver_result's try/except. Kept as module functions so tests can
|
|
142
|
+
# monkeypatch them without standing up SMTP/Twitter/Telegram.
|
|
143
|
+
|
|
144
|
+
def _config_and_container(task_agent: Any):
|
|
145
|
+
config = getattr(task_agent, "config", None)
|
|
146
|
+
container = getattr(task_agent, "container", None)
|
|
147
|
+
return config, container
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
async def _deliver_email(task_agent: Any, job: Any, final: str, deliver_target: Optional[str]) -> bool:
|
|
151
|
+
# Tenant scope: only the job owner's own address. ``deliver_target`` is honored
|
|
152
|
+
# only as an explicit user-supplied override (the agent scheduling for itself);
|
|
153
|
+
# callers that don't trust it should leave it None and rely on owner lookup.
|
|
154
|
+
to_email = deliver_target or _owner_email(task_agent, job)
|
|
155
|
+
if not to_email:
|
|
156
|
+
logger.info("cron delivery: no email recipient for job %s", getattr(job, "id", "?"))
|
|
157
|
+
return False
|
|
158
|
+
config, container = _config_and_container(task_agent)
|
|
159
|
+
from tools.email_tool import EmailTool
|
|
160
|
+
tool = EmailTool("email", config, container)
|
|
161
|
+
await tool.ensure_initialized()
|
|
162
|
+
subject = f"[POLYROB cron] {getattr(job, 'task', 'scheduled task')[:60]}"
|
|
163
|
+
return bool(await tool.send_email(to_email=to_email, subject=subject, body=final))
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def _deliver_twitter(task_agent: Any, job: Any, final: str) -> bool:
|
|
167
|
+
config, container = _config_and_container(task_agent)
|
|
168
|
+
from tools.twitter_tool import TwitterTool
|
|
169
|
+
tool = TwitterTool("twitter", config, container)
|
|
170
|
+
text = final.strip()
|
|
171
|
+
if len(text) > 280:
|
|
172
|
+
text = text[:277] + "..."
|
|
173
|
+
return bool(await tool.post(text=text))
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
async def _deliver_telegram(task_agent: Any, job: Any, final: str, deliver_target: Optional[str]) -> bool:
|
|
177
|
+
# Telegram routing goes through the bot's message sink if one is registered on
|
|
178
|
+
# the container. Best-effort: absent a router, log and skip (fail-open).
|
|
179
|
+
_, container = _config_and_container(task_agent)
|
|
180
|
+
sink = None
|
|
181
|
+
if container is not None and hasattr(container, "get_service"):
|
|
182
|
+
sink = container.get_service("telegram_sink") or container.get_service("message_router")
|
|
183
|
+
chat_id = deliver_target or _owner_telegram(task_agent, job)
|
|
184
|
+
if sink is None or not chat_id:
|
|
185
|
+
logger.info("cron delivery: telegram sink/chat unavailable for job %s", getattr(job, "id", "?"))
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
# Gate proactive send by surface send-policy (WhatsApp 24h window etc.).
|
|
189
|
+
# For Telegram (no window), resolve_proactive_send returns ("send", None) — no-op.
|
|
190
|
+
from core.surfaces.proactive import resolve_proactive_send
|
|
191
|
+
_action, _extra = await resolve_proactive_send(container, "telegram", chat_id, final)
|
|
192
|
+
if _action == "suppress":
|
|
193
|
+
logger.info("cron delivery: suppressed (send policy) for job %s", getattr(job, "id", "?"))
|
|
194
|
+
return False
|
|
195
|
+
if _action == "template":
|
|
196
|
+
# TODO(4.x): send approved template instead of suppress
|
|
197
|
+
logger.info(
|
|
198
|
+
"cron delivery: send window closed; template required (job %s) — suppressing free-text",
|
|
199
|
+
getattr(job, "id", "?"),
|
|
200
|
+
)
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
send = getattr(sink, "send_message", None)
|
|
204
|
+
if send is None:
|
|
205
|
+
return False
|
|
206
|
+
res = send(chat_id, final)
|
|
207
|
+
if hasattr(res, "__await__"):
|
|
208
|
+
res = await res
|
|
209
|
+
return bool(res)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _owner_email(task_agent: Any, job: Any) -> Optional[str]:
|
|
213
|
+
"""Resolve the job owner's email (tenant scope).
|
|
214
|
+
|
|
215
|
+
1. A registered ``user_directory`` service (real multi-user store) — the hook
|
|
216
|
+
point for a proper user store.
|
|
217
|
+
2. E3 fallback: the single configured ``POLYROB_OWNER_EMAIL`` for single-owner
|
|
218
|
+
headless deploys where nothing registers ``user_directory`` (mirrors the
|
|
219
|
+
telegram ``_configured_owner_telegram_id`` fallback). Without this, the
|
|
220
|
+
user_directory service is never registered, so ``deliver="email"`` was
|
|
221
|
+
always unreachable.
|
|
222
|
+
"""
|
|
223
|
+
container = getattr(task_agent, "container", None)
|
|
224
|
+
if container is not None and hasattr(container, "get_service"):
|
|
225
|
+
users = container.get_service("user_directory")
|
|
226
|
+
if users is not None and hasattr(users, "get_email"):
|
|
227
|
+
try:
|
|
228
|
+
addr = users.get_email(getattr(job, "user_id", None))
|
|
229
|
+
if addr:
|
|
230
|
+
return addr
|
|
231
|
+
except Exception:
|
|
232
|
+
pass
|
|
233
|
+
# 2. single-owner env fallback (SSOT in core.instance)
|
|
234
|
+
try:
|
|
235
|
+
from core.instance import resolve_owner_email
|
|
236
|
+
return resolve_owner_email()
|
|
237
|
+
except Exception:
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _configured_owner_telegram_id() -> Optional[str]:
|
|
242
|
+
"""The single configured owner telegram id, for headless single-owner deploys.
|
|
243
|
+
|
|
244
|
+
A goal/cron run started by the autonomy loops carries a non-numeric tenant id
|
|
245
|
+
(e.g. ``"rob"`` — now also the aliased tenant of the owner's OWN chat), so it
|
|
246
|
+
cannot self-resolve a telegram chat. When the deploy is owner-locked to exactly
|
|
247
|
+
ONE telegram id via ``ALLOWED_TELEGRAM_USER_IDS`` (or an explicit
|
|
248
|
+
``POLYROB_OWNER_TELEGRAM_ID``), that id IS the owner's chat.
|
|
249
|
+
|
|
250
|
+
SSOT: delegates to ``core.instance.resolve_owner_telegram_id`` — the SAME resolver
|
|
251
|
+
the inbound owner alias uses, so the id an owner-tg sender is aliased FROM and the
|
|
252
|
+
chat an out-of-band delivery is routed TO stay in lockstep.
|
|
253
|
+
"""
|
|
254
|
+
from core.instance import resolve_owner_telegram_id
|
|
255
|
+
return resolve_owner_telegram_id()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _owner_telegram(task_agent: Any, job: Any) -> Optional[str]:
|
|
259
|
+
"""Resolve the job owner's telegram chat id (tenant scope)."""
|
|
260
|
+
uid = getattr(job, "user_id", None)
|
|
261
|
+
# 1. explicit user-directory mapping (real multi-user store)
|
|
262
|
+
container = getattr(task_agent, "container", None)
|
|
263
|
+
if container is not None and hasattr(container, "get_service"):
|
|
264
|
+
users = container.get_service("user_directory")
|
|
265
|
+
if users is not None and hasattr(users, "get_telegram_chat_id"):
|
|
266
|
+
try:
|
|
267
|
+
cid = users.get_telegram_chat_id(uid)
|
|
268
|
+
if cid:
|
|
269
|
+
return str(cid)
|
|
270
|
+
except Exception:
|
|
271
|
+
pass
|
|
272
|
+
# 2. the user_id IS the telegram chat id (telegram-origin sessions).
|
|
273
|
+
if uid and str(uid).isdigit():
|
|
274
|
+
return str(uid)
|
|
275
|
+
# 3. headless autonomy run (non-numeric tenant): fall back to the single
|
|
276
|
+
# configured owner id so proactive outreach actually reaches the owner.
|
|
277
|
+
return _configured_owner_telegram_id()
|
cron/digest.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Owner daily digest — one pushed message written from evidence, not self-report.
|
|
2
|
+
|
|
3
|
+
A deterministic ($0, no-LLM) composer over the same sources the owner can already
|
|
4
|
+
audit — the unified ledger (money in/out), the durable event log (goals moved,
|
|
5
|
+
skills changed, sessions run), the goal board's open asks (pending approvals), and
|
|
6
|
+
episodes (what ran). Composed in code and pushed via the existing cron delivery
|
|
7
|
+
rail (``cron/delivery.deliver_result``), so it costs nothing and cannot
|
|
8
|
+
hallucinate. Runs as a cron job carrying ``payload.digest=true`` — the runner
|
|
9
|
+
routes such a job here instead of paying for a model turn.
|
|
10
|
+
|
|
11
|
+
Every read is a module-level seam so tests can monkeypatch it; every read fails
|
|
12
|
+
open (a missing source contributes nothing, never an exception).
|
|
13
|
+
"""
|
|
14
|
+
import time
|
|
15
|
+
from typing import Any, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _ledger(user_id: str, days: int) -> Dict[str, Any]:
|
|
19
|
+
try:
|
|
20
|
+
from core.async_bridge import run_coroutine_sync
|
|
21
|
+
from modules.credits.unified_ledger import build_ledger
|
|
22
|
+
# Loop-safe: compose_digest is awaited inside the cron runner's running
|
|
23
|
+
# loop, where a bare asyncio.run would raise and empty the money section.
|
|
24
|
+
return run_coroutine_sync(build_ledger(user_id, days=max(1, int(days)))) or {}
|
|
25
|
+
except Exception:
|
|
26
|
+
return {}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _event_aggregate(user_id: str, since_ts: Optional[float]) -> Dict[str, Any]:
|
|
30
|
+
try:
|
|
31
|
+
from agents.task.telemetry.event_log import get_event_log, event_log_enabled
|
|
32
|
+
if not event_log_enabled():
|
|
33
|
+
return {}
|
|
34
|
+
return get_event_log().aggregate(since_ts=since_ts, user_id=user_id) or {}
|
|
35
|
+
except Exception:
|
|
36
|
+
return {}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _open_asks(user_id: str, data_dir: Optional[str]) -> List[Dict[str, Any]]:
|
|
40
|
+
try:
|
|
41
|
+
import os
|
|
42
|
+
|
|
43
|
+
from agents.task.goals.board import GoalBoard
|
|
44
|
+
board = GoalBoard(os.path.join(data_dir or "data", "goals.db"))
|
|
45
|
+
rows = board.asks(user_id=user_id, status="open") or []
|
|
46
|
+
out = []
|
|
47
|
+
for a in rows:
|
|
48
|
+
get = (a.get if isinstance(a, dict) else lambda k, d=None: getattr(a, k, d))
|
|
49
|
+
out.append({"title": get("title") or get("what") or ""})
|
|
50
|
+
return out
|
|
51
|
+
except Exception:
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _episodes(user_id: str, since_ts: Optional[float]) -> List[Dict[str, Any]]:
|
|
56
|
+
try:
|
|
57
|
+
from core.async_bridge import run_coroutine_sync
|
|
58
|
+
from modules.memory.registry import memory_recall_episodes
|
|
59
|
+
rows = run_coroutine_sync(memory_recall_episodes(
|
|
60
|
+
user_id=user_id, since_ts=int(since_ts) if since_ts else None,
|
|
61
|
+
limit=20, order="newest"))
|
|
62
|
+
out = []
|
|
63
|
+
for r in rows or []:
|
|
64
|
+
get = (r.get if isinstance(r, dict) else lambda k, d=None: getattr(r, k, d))
|
|
65
|
+
out.append({"kind": get("kind"), "outcome": get("outcome")})
|
|
66
|
+
return out
|
|
67
|
+
except Exception:
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def compose_digest(user_id: str, *, days: int = 1,
|
|
72
|
+
data_dir: Optional[str] = None, db=None) -> str:
|
|
73
|
+
"""Build the digest text from evidence. Pure-ish (all reads fail-open)."""
|
|
74
|
+
since_ts = time.time() - max(1, int(days)) * 86400
|
|
75
|
+
ledger = _ledger(user_id, days)
|
|
76
|
+
agg = _event_aggregate(user_id, since_ts)
|
|
77
|
+
asks = _open_asks(user_id, data_dir)
|
|
78
|
+
episodes = _episodes(user_id, since_ts)
|
|
79
|
+
|
|
80
|
+
counts = agg.get("counts_by_kind", {}) or {}
|
|
81
|
+
n_sessions = len(episodes)
|
|
82
|
+
n_goals = int(counts.get("goal_run", 0)) + int(counts.get("goal_completion", 0))
|
|
83
|
+
n_self_mods = int(counts.get("self_modification", 0))
|
|
84
|
+
earned = float(ledger.get("earned_usd") or 0.0)
|
|
85
|
+
spent = float(ledger.get("total_spend_usd") or 0.0)
|
|
86
|
+
net = float(ledger.get("net_usd") or (earned - spent))
|
|
87
|
+
pending_n = int(ledger.get("pending_invoices") or 0)
|
|
88
|
+
pending_usd = float(ledger.get("pending_invoices_usd") or 0.0)
|
|
89
|
+
|
|
90
|
+
period = "today" if days == 1 else f"last {days}d"
|
|
91
|
+
lines = [f"Daily digest — {period}:"]
|
|
92
|
+
lines.append(f"• Activity: {n_sessions} session(s), {n_goals} goal event(s), "
|
|
93
|
+
f"{n_self_mods} self-change(s)")
|
|
94
|
+
lines.append(f"• Money: earned ${earned:.2f}, spent ${spent:.2f}, net ${net:.2f}"
|
|
95
|
+
+ (f"; {pending_n} pending invoice(s) ${pending_usd:.2f}" if pending_n else ""))
|
|
96
|
+
if asks:
|
|
97
|
+
lines.append(f"• Pending approvals ({len(asks)}):")
|
|
98
|
+
for a in asks[:5]:
|
|
99
|
+
title = (a.get("title") or "").strip().replace("\n", " ")[:80]
|
|
100
|
+
lines.append(f" - {title}")
|
|
101
|
+
else:
|
|
102
|
+
lines.append("• Pending approvals: none")
|
|
103
|
+
return "\n".join(lines)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def run_digest(task_agent: Any, job: Any) -> bool:
|
|
107
|
+
"""Compose the digest for the job's tenant and deliver it out-of-band.
|
|
108
|
+
|
|
109
|
+
A $0 tick: no model call. Returns True when a send is attempted-and-succeeds
|
|
110
|
+
(or is intentionally suppressed); False on delivery failure. Never raises."""
|
|
111
|
+
try:
|
|
112
|
+
payload = dict(getattr(job, "payload", {}) or {})
|
|
113
|
+
days = int(payload.get("days", 1) or 1)
|
|
114
|
+
data_dir = payload.get("data_dir")
|
|
115
|
+
text = await compose_digest(getattr(job, "user_id", ""), days=days, data_dir=data_dir)
|
|
116
|
+
except Exception:
|
|
117
|
+
return False
|
|
118
|
+
target = payload.get("deliver") or "telegram"
|
|
119
|
+
try:
|
|
120
|
+
from cron.delivery import deliver_result
|
|
121
|
+
return await deliver_result(task_agent, job, text, target=target,
|
|
122
|
+
deliver_target=payload.get("deliver_target"))
|
|
123
|
+
except Exception:
|
|
124
|
+
return False
|