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
cron/jobs.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""SQLite-backed cron job store (roadmap P5).
|
|
2
|
+
|
|
3
|
+
A single ``cron_jobs`` table holding durable schedule state. Uses WAL mode with a
|
|
4
|
+
jittered retry on write contention (mirrors Reference ``SessionDB`` and foreshadows
|
|
5
|
+
the P6 session-registry migration). The store is intentionally schedule-agnostic:
|
|
6
|
+
callers compute ``next_run_at`` with :mod:`cron.schedule` and hand it in.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import sqlite3
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from typing import Any, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
from core.sqlite_util import execute_retry
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class CronJob:
|
|
22
|
+
id: str
|
|
23
|
+
task: str
|
|
24
|
+
schedule_spec: str
|
|
25
|
+
user_id: str
|
|
26
|
+
next_run_at: Optional[datetime]
|
|
27
|
+
one_shot: bool = False
|
|
28
|
+
# DORMANT (ME-D2): never consumed by the runner; retained to avoid a schema
|
|
29
|
+
# migration. Wire or drop in a dedicated proposal. Not settable via
|
|
30
|
+
# CronService.schedule — always takes this default.
|
|
31
|
+
skip_memory: bool = True
|
|
32
|
+
max_duration_seconds: int = 180
|
|
33
|
+
payload: Dict[str, Any] = field(default_factory=dict)
|
|
34
|
+
enabled: bool = True
|
|
35
|
+
status: str = "scheduled" # scheduled|running|done|failed|cancelled
|
|
36
|
+
last_run_at: Optional[datetime] = None
|
|
37
|
+
created_at: Optional[datetime] = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _iso(dt: Optional[datetime]) -> Optional[str]:
|
|
41
|
+
return dt.isoformat() if dt else None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _parse(s: Optional[str]) -> Optional[datetime]:
|
|
45
|
+
return datetime.fromisoformat(s) if s else None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class CronJobStore:
|
|
49
|
+
def __init__(self, db_path: str):
|
|
50
|
+
self.db_path = db_path
|
|
51
|
+
parent = os.path.dirname(db_path)
|
|
52
|
+
if parent:
|
|
53
|
+
os.makedirs(parent, exist_ok=True)
|
|
54
|
+
self._init_schema()
|
|
55
|
+
# NOTE: crash-orphaned 'running' jobs are reclaimed by the scheduler at the
|
|
56
|
+
# start of each tick *under the held TickLock* (see CronScheduler._run_due),
|
|
57
|
+
# NOT here. Calling reclaim in __init__ ran without the lock, so a second
|
|
58
|
+
# CronJobStore built mid-tick (e.g. by the agent-facing CronJobTool, or
|
|
59
|
+
# another worker's lifespan under UVICORN_WORKERS>1) would reset a genuinely
|
|
60
|
+
# live job back to 'scheduled' and cause it to run twice.
|
|
61
|
+
|
|
62
|
+
def _init_schema(self) -> None:
|
|
63
|
+
execute_retry(
|
|
64
|
+
self.db_path,
|
|
65
|
+
"""
|
|
66
|
+
CREATE TABLE IF NOT EXISTS cron_jobs (
|
|
67
|
+
id TEXT PRIMARY KEY,
|
|
68
|
+
task TEXT NOT NULL,
|
|
69
|
+
schedule_spec TEXT NOT NULL,
|
|
70
|
+
user_id TEXT NOT NULL,
|
|
71
|
+
next_run_at TEXT,
|
|
72
|
+
one_shot INTEGER NOT NULL DEFAULT 0,
|
|
73
|
+
-- DORMANT (ME-D2): never consumed by the runner; retained to avoid
|
|
74
|
+
-- a schema migration. Wire or drop in a dedicated proposal.
|
|
75
|
+
skip_memory INTEGER NOT NULL DEFAULT 1,
|
|
76
|
+
max_duration_seconds INTEGER NOT NULL DEFAULT 180,
|
|
77
|
+
payload TEXT NOT NULL DEFAULT '{}',
|
|
78
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
79
|
+
status TEXT NOT NULL DEFAULT 'scheduled',
|
|
80
|
+
last_run_at TEXT,
|
|
81
|
+
created_at TEXT NOT NULL
|
|
82
|
+
)
|
|
83
|
+
"""
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# --- row mapping ---------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def _row_to_job(row: sqlite3.Row) -> CronJob:
|
|
90
|
+
return CronJob(
|
|
91
|
+
id=row["id"], task=row["task"], schedule_spec=row["schedule_spec"],
|
|
92
|
+
user_id=row["user_id"], next_run_at=_parse(row["next_run_at"]),
|
|
93
|
+
one_shot=bool(row["one_shot"]), skip_memory=bool(row["skip_memory"]),
|
|
94
|
+
max_duration_seconds=row["max_duration_seconds"],
|
|
95
|
+
payload=json.loads(row["payload"] or "{}"),
|
|
96
|
+
enabled=bool(row["enabled"]), status=row["status"],
|
|
97
|
+
last_run_at=_parse(row["last_run_at"]), created_at=_parse(row["created_at"]),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# --- CRUD ----------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
def add(self, job: CronJob) -> CronJob:
|
|
103
|
+
created = job.created_at or datetime(2026, 1, 1) # caller may override
|
|
104
|
+
execute_retry(
|
|
105
|
+
self.db_path,
|
|
106
|
+
"""
|
|
107
|
+
INSERT INTO cron_jobs (id, task, schedule_spec, user_id, next_run_at,
|
|
108
|
+
one_shot, skip_memory, max_duration_seconds, payload, enabled, status,
|
|
109
|
+
last_run_at, created_at)
|
|
110
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
111
|
+
""",
|
|
112
|
+
(
|
|
113
|
+
job.id, job.task, job.schedule_spec, job.user_id, _iso(job.next_run_at),
|
|
114
|
+
int(job.one_shot), int(job.skip_memory), job.max_duration_seconds,
|
|
115
|
+
json.dumps(job.payload), int(job.enabled), job.status,
|
|
116
|
+
_iso(job.last_run_at), _iso(created),
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
job.created_at = created
|
|
120
|
+
return job
|
|
121
|
+
|
|
122
|
+
def get(self, job_id: str, *, user_id: Optional[str] = None) -> Optional[CronJob]:
|
|
123
|
+
sql = "SELECT * FROM cron_jobs WHERE id=?"
|
|
124
|
+
params: tuple = (job_id,)
|
|
125
|
+
if user_id is not None:
|
|
126
|
+
sql += " AND user_id=?"
|
|
127
|
+
params = (job_id, user_id)
|
|
128
|
+
row = execute_retry(self.db_path, sql, params, fetch="one")
|
|
129
|
+
return self._row_to_job(row) if row else None
|
|
130
|
+
|
|
131
|
+
def list(self, user_id: Optional[str] = None, enabled_only: bool = False) -> List[CronJob]:
|
|
132
|
+
sql = "SELECT * FROM cron_jobs"
|
|
133
|
+
clauses, params = [], []
|
|
134
|
+
if user_id is not None:
|
|
135
|
+
clauses.append("user_id=?")
|
|
136
|
+
params.append(user_id)
|
|
137
|
+
if enabled_only:
|
|
138
|
+
clauses.append("enabled=1")
|
|
139
|
+
if clauses:
|
|
140
|
+
sql += " WHERE " + " AND ".join(clauses)
|
|
141
|
+
sql += " ORDER BY created_at"
|
|
142
|
+
rows = execute_retry(self.db_path, sql, tuple(params), fetch="all")
|
|
143
|
+
return [self._row_to_job(r) for r in rows]
|
|
144
|
+
|
|
145
|
+
def due(self, now: datetime) -> List[CronJob]:
|
|
146
|
+
rows = execute_retry(
|
|
147
|
+
self.db_path,
|
|
148
|
+
"""
|
|
149
|
+
SELECT * FROM cron_jobs
|
|
150
|
+
WHERE enabled=1 AND status IN ('scheduled')
|
|
151
|
+
AND next_run_at IS NOT NULL AND next_run_at <= ?
|
|
152
|
+
ORDER BY next_run_at
|
|
153
|
+
""",
|
|
154
|
+
(_iso(now),), fetch="all",
|
|
155
|
+
)
|
|
156
|
+
return [self._row_to_job(r) for r in rows]
|
|
157
|
+
|
|
158
|
+
def update_after_run(self, job_id: str, *, last_run_at: datetime,
|
|
159
|
+
next_run_at: Optional[datetime], status: str) -> None:
|
|
160
|
+
execute_retry(
|
|
161
|
+
self.db_path,
|
|
162
|
+
"UPDATE cron_jobs SET last_run_at=?, next_run_at=?, status=? WHERE id=?",
|
|
163
|
+
(_iso(last_run_at), _iso(next_run_at), status, job_id),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def set_status(self, job_id: str, status: str) -> None:
|
|
167
|
+
execute_retry(self.db_path, "UPDATE cron_jobs SET status=? WHERE id=?", (status, job_id))
|
|
168
|
+
|
|
169
|
+
def claim_for_run(self, job_id: str) -> bool:
|
|
170
|
+
"""Atomically transition a job 'scheduled' -> 'running'.
|
|
171
|
+
|
|
172
|
+
Returns True iff THIS caller won the claim (rowcount == 1). A compare-and-set
|
|
173
|
+
on status='scheduled' so two ticks can never both run the same job — the
|
|
174
|
+
non-CAS set_status('running') it replaces could double-run a job if a tick
|
|
175
|
+
ever raced a reclaim.
|
|
176
|
+
"""
|
|
177
|
+
rowcount = execute_retry(
|
|
178
|
+
self.db_path,
|
|
179
|
+
"UPDATE cron_jobs SET status='running' WHERE id=? AND status='scheduled'",
|
|
180
|
+
(job_id,),
|
|
181
|
+
)
|
|
182
|
+
return bool(rowcount)
|
|
183
|
+
|
|
184
|
+
def reclaim_stale_running(self) -> int:
|
|
185
|
+
"""Reset jobs stuck in 'running' (process died mid-run) back to 'scheduled'.
|
|
186
|
+
|
|
187
|
+
Returns the number of rows reclaimed. MUST be called only under the held cron
|
|
188
|
+
TickLock (the scheduler does this at the start of each tick). Ticks are
|
|
189
|
+
serialized by the lock and always write a terminal/rescheduled status after a
|
|
190
|
+
run, so any 'running' row observed under the lock is genuinely orphaned by a
|
|
191
|
+
crash and safe to reclaim. Calling it WITHOUT the lock (e.g. from __init__)
|
|
192
|
+
could reset a live job and cause a double-run.
|
|
193
|
+
"""
|
|
194
|
+
return execute_retry(
|
|
195
|
+
self.db_path,
|
|
196
|
+
"UPDATE cron_jobs SET status='scheduled' WHERE status='running' AND enabled=1",
|
|
197
|
+
(),
|
|
198
|
+
) or 0
|
|
199
|
+
|
|
200
|
+
def cancel(self, job_id: str, *, user_id: Optional[str] = None) -> bool:
|
|
201
|
+
sql = "UPDATE cron_jobs SET enabled=0, status='cancelled' WHERE id=?"
|
|
202
|
+
params: tuple = (job_id,)
|
|
203
|
+
if user_id is not None:
|
|
204
|
+
sql += " AND user_id=?"
|
|
205
|
+
params = (job_id, user_id)
|
|
206
|
+
rowcount = execute_retry(self.db_path, sql, params)
|
|
207
|
+
return bool(rowcount)
|
cron/runner.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Cron runner + ticker (roadmap P5).
|
|
2
|
+
|
|
3
|
+
Two pieces:
|
|
4
|
+
|
|
5
|
+
- :class:`CronTicker` — drives ``scheduler.tick()`` on an interval. Unit-tested
|
|
6
|
+
via an injected scheduler; the only background-loop concern is here.
|
|
7
|
+
- :func:`make_agent_runner` — the LIVE integration point that turns a ``CronJob``
|
|
8
|
+
into an agent session on the existing task-agent core, with the per-run cap. This
|
|
9
|
+
path needs a live agent run to verify (the scheduler's
|
|
10
|
+
unit tests use a fake runner), so it is opt-in via ``CRON_ENABLED`` and is not
|
|
11
|
+
wired into the default app lifecycle.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
import time
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
from typing import Any, Awaitable, Callable, Optional
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def default_cron_tools() -> list:
|
|
24
|
+
"""Posture-aware default cron toolset (WS-8). Posture 0: ['filesystem','task']
|
|
25
|
+
(byte-identical). Posture>=1: + the compute tools so a scheduled build/self-env
|
|
26
|
+
run isn't tool-starved. Mirrors ``goals.dispatcher.default_goal_tools``; resolved
|
|
27
|
+
at call time so posture 0 is unchanged."""
|
|
28
|
+
try:
|
|
29
|
+
from agents.task.goals.dispatcher import default_goal_tools
|
|
30
|
+
return default_goal_tools()
|
|
31
|
+
except Exception:
|
|
32
|
+
return ["filesystem", "task"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _cron_ev(job, outcome: str, reason: Optional[str] = None, **extra) -> None:
|
|
36
|
+
"""Emit a cron_run event to the durable event log (fail-open). Makes cron
|
|
37
|
+
lifecycle queryable in the uniform autonomy/governance stream, not just the
|
|
38
|
+
episodes table (telemetry audit 2026-07-04)."""
|
|
39
|
+
try:
|
|
40
|
+
from agents.task.telemetry.event_log import get_event_log, event_log_enabled
|
|
41
|
+
if event_log_enabled():
|
|
42
|
+
get_event_log().record(
|
|
43
|
+
"cron_run", user_id=getattr(job, "user_id", ""),
|
|
44
|
+
source="cron", job_id=getattr(job, "id", None),
|
|
45
|
+
outcome=outcome, reason=reason, **extra)
|
|
46
|
+
except Exception:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
from agents.task.runtime.run_as_session import run_task_as_session as _run_task_as_session
|
|
50
|
+
from cron.jobs import CronJob
|
|
51
|
+
|
|
52
|
+
logger = logging.getLogger(__name__)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _cron_tick_is_active(result: Any) -> bool:
|
|
56
|
+
"""A cron tick counts as 'active' (reset the idle-backoff interval to base)
|
|
57
|
+
when something actually ran or failed. A tick that was merely skipped (lock
|
|
58
|
+
contention, REPL busy) or found nothing due counts as idle. Named + module-level
|
|
59
|
+
so it's directly unit-testable without going through the async ticker loop.
|
|
60
|
+
"""
|
|
61
|
+
return bool(getattr(result, "ran", None)) or bool(getattr(result, "failed", None))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class CronTicker:
|
|
65
|
+
"""Periodically fire a scheduler tick until stopped."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, scheduler: Any, interval_seconds: int = 60):
|
|
68
|
+
self.scheduler = scheduler
|
|
69
|
+
self.interval_seconds = interval_seconds
|
|
70
|
+
|
|
71
|
+
async def tick_once(self, now: Optional[datetime] = None):
|
|
72
|
+
return await self.scheduler.tick(now=now)
|
|
73
|
+
|
|
74
|
+
async def run_forever(self, stop_event: Optional[asyncio.Event] = None) -> None:
|
|
75
|
+
from core.tickers import IntervalTicker
|
|
76
|
+
from agents.task.constants import (
|
|
77
|
+
ticker_idle_backoff_enabled,
|
|
78
|
+
ticker_idle_backoff_max_multiplier,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
is_active = None
|
|
82
|
+
max_interval = None
|
|
83
|
+
if ticker_idle_backoff_enabled():
|
|
84
|
+
is_active = _cron_tick_is_active
|
|
85
|
+
max_interval = self.interval_seconds * ticker_idle_backoff_max_multiplier()
|
|
86
|
+
|
|
87
|
+
await IntervalTicker(
|
|
88
|
+
self.scheduler.tick,
|
|
89
|
+
self.interval_seconds,
|
|
90
|
+
is_active=is_active,
|
|
91
|
+
max_interval_seconds=max_interval,
|
|
92
|
+
).run_forever(stop_event=stop_event)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def make_agent_runner(task_agent: Any, *, data_dir: str = "data") -> Callable[[CronJob], Awaitable[bool]]:
|
|
96
|
+
"""Build a runner that executes a CronJob as an isolated agent session.
|
|
97
|
+
|
|
98
|
+
LIVE PATH — reuses ``task_agent.create_session``. A cron run gets cross-session
|
|
99
|
+
memory recall like any other session; recall is tenant-scoped, so this is not
|
|
100
|
+
cross-USER contamination. Returns an async ``runner(job) -> bool`` suitable for
|
|
101
|
+
:class:`cron.scheduler.CronScheduler`. The scheduler already enforces
|
|
102
|
+
``job.max_duration_seconds`` as a hard cap.
|
|
103
|
+
"""
|
|
104
|
+
async def runner(job: CronJob) -> bool:
|
|
105
|
+
payload = dict(job.payload or {})
|
|
106
|
+
# Owner daily digest: a deterministic $0 tick composed from evidence and
|
|
107
|
+
# pushed via the delivery rail — never invokes the model. Routed here
|
|
108
|
+
# BEFORE the wake/change gates. Fail-open (a compose/deliver error is a
|
|
109
|
+
# $0 no-op, not a job failure) so a persistent hiccup can't spin.
|
|
110
|
+
if payload.get("digest"):
|
|
111
|
+
try:
|
|
112
|
+
from agents.task.constants import AutonomyConfig
|
|
113
|
+
if not AutonomyConfig.owner_digest_enabled():
|
|
114
|
+
_cron_ev(job, "skipped", "digest_disabled")
|
|
115
|
+
return True
|
|
116
|
+
from cron.digest import run_digest
|
|
117
|
+
ok = await run_digest(task_agent, job)
|
|
118
|
+
_cron_ev(job, "done" if ok else "skipped", "digest")
|
|
119
|
+
except Exception:
|
|
120
|
+
logger.warning("cron job %s: digest tick failed", job.id, exc_info=True)
|
|
121
|
+
return True
|
|
122
|
+
if not payload.get("wake_agent", True):
|
|
123
|
+
logger.info("cron job %s: wake_agent=False — $0 tick, agent not invoked", job.id)
|
|
124
|
+
_cron_ev(job, "skipped", "wake_agent_false")
|
|
125
|
+
return True
|
|
126
|
+
# Wake change-gate: a change-gated review tick whose state fingerprint is
|
|
127
|
+
# unchanged since the last SUCCESSFUL run is a $0 tick — same shape as
|
|
128
|
+
# wake_agent=False. Fail-open (any gate error runs the tick); delivery
|
|
129
|
+
# jobs are never gated. The outcome-tagged baseline is recorded AFTER the
|
|
130
|
+
# run (finally below): a failed run never establishes a skippable
|
|
131
|
+
# baseline, so a persistently-failing job always retries.
|
|
132
|
+
_gate_active = False
|
|
133
|
+
try:
|
|
134
|
+
from cron.wake_gate import gate_applies, should_skip_wake
|
|
135
|
+
_gate_active = gate_applies(job)
|
|
136
|
+
if _gate_active and should_skip_wake(job, data_dir=data_dir):
|
|
137
|
+
logger.info("cron job %s: no observable change — $0 tick, agent not invoked", job.id)
|
|
138
|
+
_cron_ev(job, "skipped", "no_change")
|
|
139
|
+
return True
|
|
140
|
+
except Exception:
|
|
141
|
+
logger.warning("cron job %s: wake gate error — running tick", job.id, exc_info=True)
|
|
142
|
+
ok = False
|
|
143
|
+
try:
|
|
144
|
+
ok = await _execute(job, payload)
|
|
145
|
+
return ok
|
|
146
|
+
finally:
|
|
147
|
+
if _gate_active:
|
|
148
|
+
try:
|
|
149
|
+
from cron.wake_gate import record_wake_outcome
|
|
150
|
+
record_wake_outcome(job, data_dir=data_dir, ok=bool(ok))
|
|
151
|
+
except Exception:
|
|
152
|
+
logger.warning("cron job %s: wake gate outcome record failed", job.id,
|
|
153
|
+
exc_info=True)
|
|
154
|
+
|
|
155
|
+
async def _execute(job: CronJob, payload: dict) -> bool:
|
|
156
|
+
session_id = None
|
|
157
|
+
_t0 = time.time()
|
|
158
|
+
_cron_ev(job, "started")
|
|
159
|
+
from core.runtime_config import resolve_runtime_config
|
|
160
|
+
provider = payload.get("provider") or resolve_runtime_config(None, None)[0]
|
|
161
|
+
# Fill the provider's default model from the registry when the job doesn't pin
|
|
162
|
+
# one — an autonomous run has no interactive config, and a None model crashes
|
|
163
|
+
# session setup downstream ('.lower()' on None). Mirrors the goal dispatcher.
|
|
164
|
+
model = payload.get("model")
|
|
165
|
+
if not model:
|
|
166
|
+
try:
|
|
167
|
+
from modules.llm.llm_client_registry import get_default_model
|
|
168
|
+
model = get_default_model(provider)
|
|
169
|
+
except Exception:
|
|
170
|
+
model = None
|
|
171
|
+
request = {
|
|
172
|
+
"task": job.task,
|
|
173
|
+
"provider": provider,
|
|
174
|
+
"model": model,
|
|
175
|
+
"tools": payload.get("tools", default_cron_tools()),
|
|
176
|
+
"max_steps": payload.get("max_steps", 20),
|
|
177
|
+
"temperature": 0.0,
|
|
178
|
+
"cron": True,
|
|
179
|
+
}
|
|
180
|
+
try:
|
|
181
|
+
from agents.task.constants import AutonomyConfig
|
|
182
|
+
|
|
183
|
+
# Legacy branch (CRON_RUN_LOOP=OFF): create-only, return bool(session_info).
|
|
184
|
+
# Kept exactly as-is — run_task_as_session does NOT apply here.
|
|
185
|
+
if not AutonomyConfig.cron_run_loop():
|
|
186
|
+
session_info = await task_agent.create_session(user_id=job.user_id, request=request)
|
|
187
|
+
return bool(session_info)
|
|
188
|
+
|
|
189
|
+
# W3 LIVE-BUG FIX: route through the shared helper so create_session AND
|
|
190
|
+
# run_session are both called and refusals are normalised to (sid, None).
|
|
191
|
+
session_id, final = await _run_task_as_session(
|
|
192
|
+
task_agent, user_id=job.user_id, request=request, autonomous=True
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Back-half: cron-specific log messages + early returns.
|
|
196
|
+
if session_id is None:
|
|
197
|
+
logger.error("cron job %s: create_session returned no id", job.id)
|
|
198
|
+
return False
|
|
199
|
+
if final is None:
|
|
200
|
+
# run_task_as_session already collapsed refusal/empty → None; we emit the
|
|
201
|
+
# original log so operators see one clear refusal line (no double-logging:
|
|
202
|
+
# run_task_as_session is intentionally silent on refusals).
|
|
203
|
+
logger.warning("cron job %s: run did not complete (refusal or empty)", job.id)
|
|
204
|
+
try:
|
|
205
|
+
from modules.memory.episodic import finalize_episode
|
|
206
|
+
await finalize_episode(
|
|
207
|
+
session_id=session_id, user_id=job.user_id, kind="cron",
|
|
208
|
+
task=job.task, outcome="failed",
|
|
209
|
+
meta={"source": "cron", "job_id": job.id},
|
|
210
|
+
)
|
|
211
|
+
except Exception:
|
|
212
|
+
logger.warning("cron episodic write failed", exc_info=True)
|
|
213
|
+
_cron_ev(job, "failed", "refusal_or_empty", duration_s=round(time.time() - _t0, 3))
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
# Episodic write happens BEFORE out-of-band delivery (Task 7): delivery's
|
|
217
|
+
# surfaced-mark is a plain UPDATE keyed on session_id, so the row must
|
|
218
|
+
# already exist or the mark is a silent no-op. Order matters here.
|
|
219
|
+
try:
|
|
220
|
+
from modules.memory.episodic import finalize_episode, collect_provenance
|
|
221
|
+
orchestrator = task_agent.get_orchestrator(session_id)
|
|
222
|
+
prov = await collect_provenance(orchestrator)
|
|
223
|
+
await finalize_episode(
|
|
224
|
+
session_id=session_id, user_id=job.user_id, kind="cron",
|
|
225
|
+
task=job.task, outcome="done",
|
|
226
|
+
summary=str(final)[:2000] if final is not None else None,
|
|
227
|
+
spend_usd=prov["spend_usd"], steps=prov["steps"],
|
|
228
|
+
artifacts=prov["artifacts"],
|
|
229
|
+
meta={"source": "cron", "job_id": job.id},
|
|
230
|
+
)
|
|
231
|
+
_cron_ev(job, "done", duration_s=round(time.time() - _t0, 3),
|
|
232
|
+
spend_usd=prov.get("spend_usd"), steps=prov.get("steps"))
|
|
233
|
+
except Exception:
|
|
234
|
+
logger.warning("cron episodic write failed", exc_info=True)
|
|
235
|
+
|
|
236
|
+
# Out-of-band delivery (gated CRON_DELIVERY_ENABLED, default OFF). Runs
|
|
237
|
+
# inside the scheduler's wait_for budget; fail-open — never fails the job.
|
|
238
|
+
deliver = payload.get("deliver")
|
|
239
|
+
if AutonomyConfig.cron_delivery_enabled() and deliver and final:
|
|
240
|
+
try:
|
|
241
|
+
from cron.delivery import deliver_result, delivery_outcome
|
|
242
|
+
ok = await deliver_result(
|
|
243
|
+
task_agent, job, final,
|
|
244
|
+
target=deliver, deliver_target=payload.get("deliver_target"),
|
|
245
|
+
session_id=session_id,
|
|
246
|
+
)
|
|
247
|
+
# Observability: make proactive delivery verifiable in the journal.
|
|
248
|
+
# outcome distinguishes a [SILENT] opt-out (suppressed) from a real
|
|
249
|
+
# send failure (failed) — they used to both log as ok=False.
|
|
250
|
+
logger.info("cron job %s out-of-band delivery target=%s outcome=%s",
|
|
251
|
+
job.id, deliver, delivery_outcome(final, ok))
|
|
252
|
+
except Exception as e: # belt-and-suspenders; delivery is best-effort
|
|
253
|
+
logger.error("cron job %s delivery error: %s", job.id, e, exc_info=True)
|
|
254
|
+
|
|
255
|
+
return True
|
|
256
|
+
except Exception as e:
|
|
257
|
+
logger.error("cron job %s session failed: %s", job.id, e, exc_info=True)
|
|
258
|
+
_cron_ev(job, "failed", "exception", duration_s=round(time.time() - _t0, 3),
|
|
259
|
+
error=str(e)[:200])
|
|
260
|
+
if session_id:
|
|
261
|
+
try:
|
|
262
|
+
from modules.memory.episodic import finalize_episode
|
|
263
|
+
await finalize_episode(
|
|
264
|
+
session_id=session_id, user_id=job.user_id, kind="cron",
|
|
265
|
+
task=job.task, outcome="failed", summary=str(e)[:2000],
|
|
266
|
+
meta={"source": "cron", "job_id": job.id},
|
|
267
|
+
)
|
|
268
|
+
except Exception:
|
|
269
|
+
logger.warning("cron episodic write failed", exc_info=True)
|
|
270
|
+
return False
|
|
271
|
+
|
|
272
|
+
return runner
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def build_cron_ticker(
|
|
276
|
+
task_agent: Any,
|
|
277
|
+
*,
|
|
278
|
+
data_dir: str = "data",
|
|
279
|
+
interval_seconds: int = 60,
|
|
280
|
+
) -> CronTicker:
|
|
281
|
+
"""Assemble the full cron stack into a :class:`CronTicker` (B-T1).
|
|
282
|
+
|
|
283
|
+
Shares the on-disk job store with the agent-facing ``cronjob`` tool — both use
|
|
284
|
+
``<data_dir>/cron.db`` — so jobs scheduled by the tool are picked up by the
|
|
285
|
+
ticker. The tick lock (``<data_dir>/cron.tick.lock``) keeps a tick safe under
|
|
286
|
+
``UVICORN_WORKERS>1``. Opt-in: the FastAPI lifespan only calls this when
|
|
287
|
+
``CRON_ENABLED`` is set, so prod is unchanged by default.
|
|
288
|
+
"""
|
|
289
|
+
from cron.jobs import CronJobStore
|
|
290
|
+
from cron.scheduler import CronScheduler
|
|
291
|
+
|
|
292
|
+
store = CronJobStore(os.path.join(data_dir, "cron.db"))
|
|
293
|
+
runner = make_agent_runner(task_agent, data_dir=data_dir)
|
|
294
|
+
scheduler = CronScheduler(
|
|
295
|
+
store, runner, lock_path=os.path.join(data_dir, "cron.tick.lock")
|
|
296
|
+
)
|
|
297
|
+
return CronTicker(scheduler, interval_seconds=interval_seconds)
|