polyrob 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agents/README.md +453 -0
- agents/__init__.py +164 -0
- agents/base_agent.py +743 -0
- agents/personality/character.py +119 -0
- agents/personality/character_manager.py +309 -0
- agents/personality/characters/rob.character.json +56 -0
- agents/personality/characters/trump.character.json +63 -0
- agents/personality/persona_render.py +78 -0
- agents/personality/persona_resolver.py +115 -0
- agents/prompt/__init__.py +58 -0
- agents/prompt/base_prompt.py +92 -0
- agents/prompt/system.py +417 -0
- agents/task/README_PATH_MANAGEMENT.md +141 -0
- agents/task/__init__.py +162 -0
- agents/task/agent/__init__.py +70 -0
- agents/task/agent/agent_state.py +408 -0
- agents/task/agent/async_delegation.py +274 -0
- agents/task/agent/autonomy_state.py +275 -0
- agents/task/agent/conversation.py +75 -0
- agents/task/agent/core/__init__.py +6 -0
- agents/task/agent/core/aux_metering.py +61 -0
- agents/task/agent/core/background_review.py +157 -0
- agents/task/agent/core/construction.py +1179 -0
- agents/task/agent/core/conversational_exit.py +49 -0
- agents/task/agent/core/correspondent_gate.py +227 -0
- agents/task/agent/core/curator.py +246 -0
- agents/task/agent/core/episodic_digest.py +103 -0
- agents/task/agent/core/error_recovery.py +480 -0
- agents/task/agent/core/history_io.py +257 -0
- agents/task/agent/core/llm_provisioning.py +287 -0
- agents/task/agent/core/llm_runner.py +547 -0
- agents/task/agent/core/logging_io.py +276 -0
- agents/task/agent/core/loop_detection.py +262 -0
- agents/task/agent/core/memory_prefetch.py +325 -0
- agents/task/agent/core/memory_writer.py +667 -0
- agents/task/agent/core/model_introspection.py +82 -0
- agents/task/agent/core/model_swap.py +177 -0
- agents/task/agent/core/next_action_internal.py +1331 -0
- agents/task/agent/core/output_validation.py +303 -0
- agents/task/agent/core/project_context.py +272 -0
- agents/task/agent/core/resources.py +93 -0
- agents/task/agent/core/result_processing.py +389 -0
- agents/task/agent/core/run_loop.py +661 -0
- agents/task/agent/core/safety_lifecycle.py +228 -0
- agents/task/agent/core/secret_guard.py +293 -0
- agents/task/agent/core/self_wake.py +280 -0
- agents/task/agent/core/session_metadata.py +104 -0
- agents/task/agent/core/step.py +781 -0
- agents/task/agent/core/step_execution.py +398 -0
- agents/task/agent/core/step_telemetry.py +222 -0
- agents/task/agent/core/test_aux_metering.py +50 -0
- agents/task/agent/core/test_billing_failover.py +68 -0
- agents/task/agent/core/test_fatal_error_classifier.py +38 -0
- agents/task/agent/core/test_judge_validation.py +275 -0
- agents/task/agent/core/test_model_swap.py +190 -0
- agents/task/agent/core/test_reflection_offload.py +54 -0
- agents/task/agent/core/turn_input.py +8 -0
- agents/task/agent/core/untrusted_wrap.py +103 -0
- agents/task/agent/core/user_ingress.py +228 -0
- agents/task/agent/hitl_manager.py +396 -0
- agents/task/agent/log_sanitize.py +36 -0
- agents/task/agent/message_manager/config.py +97 -0
- agents/task/agent/message_manager/service.py +758 -0
- agents/task/agent/message_manager/tests.py +305 -0
- agents/task/agent/message_manager/tool_call_builder.py +539 -0
- agents/task/agent/message_manager/tool_message_repair.py +364 -0
- agents/task/agent/message_manager/views.py +143 -0
- agents/task/agent/messages/__init__.py +8 -0
- agents/task/agent/messages/builders.py +309 -0
- agents/task/agent/messages/compactor.py +611 -0
- agents/task/agent/messages/context_references.py +271 -0
- agents/task/agent/messages/filters.py +333 -0
- agents/task/agent/messages/guidance.py +328 -0
- agents/task/agent/messages/persistence.py +448 -0
- agents/task/agent/messages/retrieval.py +506 -0
- agents/task/agent/messages/sqlite_persistence.py +85 -0
- agents/task/agent/messages/test_compaction_upgrade.py +349 -0
- agents/task/agent/messages/test_persistence_subdir_fix.py +28 -0
- agents/task/agent/messages/test_sqlite_persistence.py +54 -0
- agents/task/agent/messages/token_counter.py +532 -0
- agents/task/agent/orchestrator.py +776 -0
- agents/task/agent/profile_manager.py +105 -0
- agents/task/agent/profile_registry.py +173 -0
- agents/task/agent/prompts.py +1090 -0
- agents/task/agent/scenario_registry.py +226 -0
- agents/task/agent/service.py +509 -0
- agents/task/agent/session.py +984 -0
- agents/task/agent/skill_discovery.py +110 -0
- agents/task/agent/skill_frontmatter.py +94 -0
- agents/task/agent/skill_manager.py +1201 -0
- agents/task/agent/skill_store.py +627 -0
- agents/task/agent/skill_validation.py +56 -0
- agents/task/agent/skill_writer.py +492 -0
- agents/task/agent/sub_agent_manager.py +1137 -0
- agents/task/agent/test_session_disk_recovery.py +34 -0
- agents/task/agent/tests.py +513 -0
- agents/task/agent/tool_call_tracker.py +511 -0
- agents/task/agent/views.py +469 -0
- agents/task/config.py +432 -0
- agents/task/constants.py +1384 -0
- agents/task/flag_defaults.py +63 -0
- agents/task/goals/__init__.py +1 -0
- agents/task/goals/autonomy_marker.py +28 -0
- agents/task/goals/board.py +690 -0
- agents/task/goals/completion_judge.py +247 -0
- agents/task/goals/context.py +62 -0
- agents/task/goals/dispatcher.py +848 -0
- agents/task/goals/escalation.py +102 -0
- agents/task/goals/planner.py +114 -0
- agents/task/logging_config.py +155 -0
- agents/task/path.py +991 -0
- agents/task/robust_parse_config.py +400 -0
- agents/task/runtime/__init__.py +0 -0
- agents/task/runtime/run_as_session.py +130 -0
- agents/task/runtime_safety.py +308 -0
- agents/task/session/__init__.py +5 -0
- agents/task/session/browser_pool.py +130 -0
- agents/task/session/cleanup.py +612 -0
- agents/task/session/execution.py +397 -0
- agents/task/session/feed.py +200 -0
- agents/task/session/hitl_ingress.py +201 -0
- agents/task/session/hooks.py +83 -0
- agents/task/session/multi_agent.py +243 -0
- agents/task/session/workspace.py +91 -0
- agents/task/session_registry.py +91 -0
- agents/task/session_route.py +35 -0
- agents/task/sqlite_session_registry.py +261 -0
- agents/task/surface_config.py +185 -0
- agents/task/telemetry/__init__.py +58 -0
- agents/task/telemetry/event_log.py +180 -0
- agents/task/telemetry/formatters.py +642 -0
- agents/task/telemetry/manager.py +571 -0
- agents/task/telemetry/memory_events.py +66 -0
- agents/task/telemetry/self_events.py +53 -0
- agents/task/telemetry/sequence.py +160 -0
- agents/task/telemetry/service.py +1265 -0
- agents/task/telemetry/views.py +882 -0
- agents/task/templates.py +98 -0
- agents/task/test_aux_chain.py +151 -0
- agents/task/test_aux_router.py +41 -0
- agents/task/tests/test_parse_robustness.py +170 -0
- agents/task/tool_defaults.py +110 -0
- agents/task/utils.py +762 -0
- agents/task/utils_json.py +768 -0
- agents/task/utils_webview.py +86 -0
- agents/task/workspace_context.py +229 -0
- agents/task_agent_lite.py +2398 -0
- api/README.md +683 -0
- api/TASK_API_DOCS.md +151 -0
- api/__init__.py +0 -0
- api/a2a/__init__.py +37 -0
- api/a2a/agent_card.py +337 -0
- api/a2a/client.py +657 -0
- api/a2a/endpoints.py +375 -0
- api/a2a/models.py +268 -0
- api/a2a/streaming.py +419 -0
- api/a2a/task_handler.py +929 -0
- api/admin_endpoints.py +1584 -0
- api/app.py +923 -0
- api/auth_constants.py +28 -0
- api/auth_endpoints.py +402 -0
- api/auth_state.py +36 -0
- api/chat_via_task.py +42 -0
- api/conversation_manager.py +231 -0
- api/dependencies.py +186 -0
- api/eip8004_endpoints.py +452 -0
- api/hyperliquid_models.py +174 -0
- api/hyperliquid_routes.py +450 -0
- api/interfaces.py +207 -0
- api/jwt_middleware.py +83 -0
- api/kb/__init__.py +1 -0
- api/kb/endpoints.py +314 -0
- api/mcp_models.py +279 -0
- api/mcp_routes.py +438 -0
- api/middleware.py +526 -0
- api/models.py +176 -0
- api/openai_compat/__init__.py +0 -0
- api/openai_compat/model_map.py +25 -0
- api/openai_compat/models.py +59 -0
- api/openai_compat/router.py +140 -0
- api/payment_endpoints.py +444 -0
- api/payment_verification.py +138 -0
- api/polymarket_models.py +155 -0
- api/polymarket_routes.py +501 -0
- api/pricing_endpoints.py +89 -0
- api/session_routing.py +54 -0
- api/skill_endpoints.py +453 -0
- api/task_http_api.py +1856 -0
- api/webhooks.py +52 -0
- api/x402_endpoints.py +280 -0
- avatar/README.md +44 -0
- avatar/__init__.py +1 -0
- avatar/config/rob.json +7 -0
- avatar/mindprint.js +395 -0
- avatar/renders/rob.meta.json +20 -0
- avatar/renders/rob.png +0 -0
- avatar/studio.html +373 -0
- avatar/webview/avatar-live.js +44 -0
- cli/README.md +89 -0
- cli/__init__.py +0 -0
- cli/cli_surface.py +67 -0
- cli/commands/__init__.py +0 -0
- cli/commands/_bootstrap.py +49 -0
- cli/commands/_errors.py +53 -0
- cli/commands/chat.py +973 -0
- cli/commands/config.py +100 -0
- cli/commands/dashboard.py +103 -0
- cli/commands/doctor.py +222 -0
- cli/commands/email.py +174 -0
- cli/commands/gateway.py +346 -0
- cli/commands/goals.py +509 -0
- cli/commands/init.py +276 -0
- cli/commands/journey.py +30 -0
- cli/commands/kb.py +280 -0
- cli/commands/model.py +107 -0
- cli/commands/owner.py +431 -0
- cli/commands/pfp.py +232 -0
- cli/commands/run.py +414 -0
- cli/commands/serve.py +41 -0
- cli/commands/session.py +684 -0
- cli/commands/skill_install.py +786 -0
- cli/commands/skills.py +128 -0
- cli/commands/subagents.py +100 -0
- cli/commands/surface.py +81 -0
- cli/commands/telegram.py +185 -0
- cli/commands/todos.py +209 -0
- cli/commands/tools.py +102 -0
- cli/commands/update.py +316 -0
- cli/commands/whatsapp.py +175 -0
- cli/config_store.py +276 -0
- cli/gitignore.py +43 -0
- cli/inventory.py +46 -0
- cli/keys.py +101 -0
- cli/persona.py +17 -0
- cli/polyrob.py +137 -0
- cli/ui/__init__.py +64 -0
- cli/ui/activity.py +141 -0
- cli/ui/app.py +538 -0
- cli/ui/banner.py +233 -0
- cli/ui/blocks.py +534 -0
- cli/ui/bootstrap_notice.py +47 -0
- cli/ui/commands/__init__.py +41 -0
- cli/ui/commands/h_cron.py +64 -0
- cli/ui/commands/h_journey.py +164 -0
- cli/ui/commands/h_kb.py +121 -0
- cli/ui/commands/h_learn.py +100 -0
- cli/ui/commands/h_mcp.py +215 -0
- cli/ui/commands/h_self.py +113 -0
- cli/ui/commands/h_skills.py +314 -0
- cli/ui/commands/handlers.py +1672 -0
- cli/ui/commands/registry.py +426 -0
- cli/ui/dialog.py +425 -0
- cli/ui/event_registry.py +110 -0
- cli/ui/event_specs.py +45 -0
- cli/ui/events.py +433 -0
- cli/ui/identity.py +15 -0
- cli/ui/lifecycle.py +159 -0
- cli/ui/live_hooks.py +43 -0
- cli/ui/model_selector.py +532 -0
- cli/ui/persistent_loop.py +181 -0
- cli/ui/pick.py +113 -0
- cli/ui/plain_renderer.py +351 -0
- cli/ui/renderer.py +384 -0
- cli/ui/rich_renderer.py +533 -0
- cli/ui/secrets.py +115 -0
- cli/ui/state.py +376 -0
- cli/ui/statusbar.py +231 -0
- cli/ui/streaming.py +254 -0
- cli/ui/terminal_render.py +105 -0
- cli/ui/theme.py +132 -0
- cli/update/__init__.py +6 -0
- cli/update/context.py +121 -0
- cli/update/detect.py +176 -0
- cli/update/engine.py +95 -0
- cli/update/migrate_guarded.py +55 -0
- cli/update/process_guard.py +215 -0
- cli/update/runners.py +78 -0
- cli/update/snapshot.py +270 -0
- cli/update/versions.py +205 -0
- core/README.md +377 -0
- core/__init__.py +174 -0
- core/assets.py +87 -0
- core/async_bridge.py +76 -0
- core/autonomy_runtime.py +259 -0
- core/base_component.py +302 -0
- core/bootstrap.py +681 -0
- core/bot.py +310 -0
- core/config.py +833 -0
- core/constants.py +126 -0
- core/container.py +462 -0
- core/db_manifest.py +101 -0
- core/embedding.py +57 -0
- core/env.py +39 -0
- core/exceptions.py +287 -0
- core/flags.py +144 -0
- core/flags_catalog.py +345 -0
- core/home_migration.py +62 -0
- core/identity.py +118 -0
- core/initialization.py +1004 -0
- core/instance.py +537 -0
- core/interactive_gate.py +96 -0
- core/logging.py +436 -0
- core/owner_doc_writer.py +176 -0
- core/pairing.py +193 -0
- core/path_safety.py +20 -0
- core/paths.py +27 -0
- core/payment_config.py +21 -0
- core/permissions.py +322 -0
- core/runtime_config.py +96 -0
- core/runtime_paths.py +135 -0
- core/seams.py +35 -0
- core/secret_scan.py +25 -0
- core/secret_scrub.py +71 -0
- core/secrets.py +35 -0
- core/security_logging_filter.py +148 -0
- core/self_context_writer.py +333 -0
- core/self_evolution.py +294 -0
- core/session_context.py +47 -0
- core/sqlite_util.py +59 -0
- core/surfaces/__init__.py +16 -0
- core/surfaces/access.py +118 -0
- core/surfaces/binding.py +88 -0
- core/surfaces/bootstrap.py +78 -0
- core/surfaces/circuit.py +168 -0
- core/surfaces/continuity.py +77 -0
- core/surfaces/correspondents.py +228 -0
- core/surfaces/dispatcher.py +216 -0
- core/surfaces/envelopes.py +75 -0
- core/surfaces/gc.py +30 -0
- core/surfaces/idempotency.py +57 -0
- core/surfaces/inbound_webhook.py +124 -0
- core/surfaces/media.py +32 -0
- core/surfaces/message_router.py +91 -0
- core/surfaces/outbound_allowlist.py +80 -0
- core/surfaces/outbound_dispatcher.py +110 -0
- core/surfaces/outbound_mirror.py +37 -0
- core/surfaces/outbound_queue.py +107 -0
- core/surfaces/outbound_target.py +12 -0
- core/surfaces/owner_admin.py +29 -0
- core/surfaces/proactive.py +78 -0
- core/surfaces/progress.py +108 -0
- core/surfaces/rate_bucket.py +21 -0
- core/surfaces/registry.py +51 -0
- core/surfaces/rendering.py +53 -0
- core/surfaces/send_policy.py +14 -0
- core/surfaces/serialize.py +24 -0
- core/surfaces/session_chat_registry.py +112 -0
- core/surfaces/session_policy.py +60 -0
- core/surfaces/surface.py +288 -0
- core/surfaces/transcription.py +88 -0
- core/surfaces/voice_echo.py +26 -0
- core/surfaces/voice_guard.py +23 -0
- core/tickers.py +174 -0
- core/tool_catalog.py +192 -0
- core/version.py +75 -0
- core/wallet/__init__.py +5 -0
- core/wallet/agent_wallet.py +59 -0
- core/wallet/audit_sink.py +61 -0
- core/wallet/config.py +70 -0
- core/wallet/factory.py +87 -0
- core/wallet/policy.py +104 -0
- core/wallet/signer.py +51 -0
- cron/README.md +65 -0
- cron/__init__.py +0 -0
- cron/delivery.py +277 -0
- cron/digest.py +124 -0
- cron/jobs.py +207 -0
- cron/runner.py +297 -0
- cron/schedule.py +194 -0
- cron/scheduler.py +201 -0
- cron/service.py +57 -0
- cron/wake_gate.py +223 -0
- data/__init__.py +1 -0
- data/prompts/autov2_prompts.json +87 -0
- data/prompts/skills/browser-automation/SKILL.md +43 -0
- data/prompts/skills/coding-workflow/SKILL.md +36 -0
- data/prompts/skills/crypto-trading-safety/SKILL.md +53 -0
- data/prompts/skills/document-writing/SKILL.md +33 -0
- data/prompts/skills/email-comms/SKILL.md +33 -0
- data/prompts/skills/file-data-ops/SKILL.md +35 -0
- data/prompts/skills/hyperliquid-account-review/SKILL.md +48 -0
- data/prompts/skills/hyperliquid-market-data/SKILL.md +52 -0
- data/prompts/skills/hyperliquid-trading/SKILL.md +59 -0
- data/prompts/skills/lead-research/SKILL.md +86 -0
- data/prompts/skills/market-research-brief/SKILL.md +104 -0
- data/prompts/skills/person-analyzer/SKILL.md +124 -0
- data/prompts/skills/polymarket-market-research/SKILL.md +54 -0
- data/prompts/skills/polymarket-portfolio-review/SKILL.md +50 -0
- data/prompts/skills/polymarket-trading/SKILL.md +55 -0
- data/prompts/skills/presentation-creator/SKILL.md +36 -0
- data/prompts/skills/project-analyzer/SKILL.md +169 -0
- data/prompts/skills/rules.json +268 -0
- data/prompts/skills/secret-handling/SKILL.md +20 -0
- data/prompts/skills/skill-authoring/SKILL.md +31 -0
- data/prompts/skills/skill-security-review/SKILL.md +27 -0
- data/prompts/skills/social-discovery/SKILL.md +57 -0
- data/prompts/skills/task-planning/SKILL.md +33 -0
- data/prompts/skills/web-research/SKILL.md +39 -0
- data/prompts/skills/web-scraping/SKILL.md +73 -0
- data/prompts/skills/x-engagement/SKILL.md +69 -0
- data/prompts/system_prompts.json +4 -0
- data/subscription_channels.json +5 -0
- modules/README.md +503 -0
- modules/__init__.py +124 -0
- modules/auth/__init__.py +13 -0
- modules/auth/api_key_manager.py +157 -0
- modules/auth/identity_mapper.py +380 -0
- modules/auth/siwe_auth.py +273 -0
- modules/auth/tier_manager.py +173 -0
- modules/base_module.py +190 -0
- modules/credits/__init__.py +29 -0
- modules/credits/balance_manager.py +237 -0
- modules/credits/cost_utils.py +129 -0
- modules/credits/pricing.py +139 -0
- modules/credits/unified_ledger.py +148 -0
- modules/credits/usage_meter.py +310 -0
- modules/credits/usage_tracker.py +674 -0
- modules/database/__init__.py +33 -0
- modules/database/audit_log.py +322 -0
- modules/database/auth_tables.py +276 -0
- modules/database/connection.py +644 -0
- modules/database/connection_pool.py +305 -0
- modules/database/conversation_contexts.py +446 -0
- modules/database/database_manager.py +237 -0
- modules/database/hyperliquid.py +375 -0
- modules/database/polymarket.py +574 -0
- modules/database/user_mcp_servers.py +728 -0
- modules/database/user_profiles.py +562 -0
- modules/database/utils.py +107 -0
- modules/database/x402_tables.py +92 -0
- modules/eip8004/README.md +1069 -0
- modules/eip8004/__init__.py +65 -0
- modules/eip8004/contracts.py +498 -0
- modules/eip8004/models.py +271 -0
- modules/eip8004/registration.py +148 -0
- modules/eip8004/reputation.py +367 -0
- modules/eip8004/validation.py +277 -0
- modules/llm/__init__.py +97 -0
- modules/llm/adapters.py +963 -0
- modules/llm/anthropic_client.py +831 -0
- modules/llm/available_models.py +106 -0
- modules/llm/brain_scrubber.py +137 -0
- modules/llm/cache_hints.py +162 -0
- modules/llm/deepseek_client.py +562 -0
- modules/llm/gemini_client.py +1367 -0
- modules/llm/llm_client.py +1139 -0
- modules/llm/llm_client_registry.py +188 -0
- modules/llm/llm_factory.py +237 -0
- modules/llm/llm_manager.py +1036 -0
- modules/llm/messages.py +374 -0
- modules/llm/model_registry.py +1935 -0
- modules/llm/nvidia_client.py +125 -0
- modules/llm/openai_client.py +800 -0
- modules/llm/openrouter_client.py +969 -0
- modules/llm/profiles.py +178 -0
- modules/llm/test_anthropic_cache_capture.py +35 -0
- modules/llm/test_cached_pricing_derivation.py +24 -0
- modules/llm/think_scrubber.py +444 -0
- modules/llm/token_counter.py +268 -0
- modules/memory/__init__.py +35 -0
- modules/memory/backend_factory.py +83 -0
- modules/memory/cache_manager.py +186 -0
- modules/memory/episodic.py +108 -0
- modules/memory/local_vector_memory_provider.py +552 -0
- modules/memory/memory_manager.py +225 -0
- modules/memory/models.py +312 -0
- modules/memory/provider.py +210 -0
- modules/memory/registry.py +205 -0
- modules/memory/sqlite_memory_provider.py +767 -0
- modules/memory/task/__init__.py +52 -0
- modules/memory/task/compaction_manager.py +157 -0
- modules/memory/task/context_retriever.py +809 -0
- modules/memory/task/hierarchical_memory.py +1002 -0
- modules/memory/task/lexical_retriever.py +71 -0
- modules/memory/task/null_context_manager.py +213 -0
- modules/memory/task/phase_manager.py +625 -0
- modules/memory/task/reflection_service.py +157 -0
- modules/memory/task/semantic_retriever.py +218 -0
- modules/memory/task/task_context_manager.py +1165 -0
- modules/memory/task/test_forgetting_engages.py +24 -0
- modules/memory/task/test_reflection_llm.py +39 -0
- modules/memory/task/test_threat_scan.py +53 -0
- modules/memory/task/threat_scan.py +90 -0
- modules/memory/test_sqlite_memory_provider.py +106 -0
- modules/memory/user_profile_manager.py +527 -0
- modules/payments/__init__.py +11 -0
- modules/payments/deposit_monitor.py +478 -0
- modules/payments/price_oracle.py +70 -0
- modules/payments/treasury_sweeper.py +401 -0
- modules/payments/wallet_generator.py +145 -0
- modules/pfp/__init__.py +1 -0
- modules/pfp/config.py +92 -0
- modules/pfp/mesh.py +565 -0
- modules/pfp/push.py +69 -0
- modules/pfp/renderer.py +85 -0
- modules/pfp/store.py +82 -0
- modules/skills/__init__.py +1 -0
- modules/skills/skill_usage.py +248 -0
- modules/transcription/__init__.py +24 -0
- modules/transcription/base.py +20 -0
- modules/transcription/faster_whisper_transcriber.py +55 -0
- modules/x402/README.md +249 -0
- modules/x402/__init__.py +22 -0
- modules/x402/invoicing.py +416 -0
- modules/x402/middleware.py +364 -0
- modules/x402/settlement_watcher.py +145 -0
- modules/x402/x402_integration.py +344 -0
- polyrob-0.5.0.dist-info/METADATA +482 -0
- polyrob-0.5.0.dist-info/RECORD +745 -0
- polyrob-0.5.0.dist-info/WHEEL +5 -0
- polyrob-0.5.0.dist-info/entry_points.txt +2 -0
- polyrob-0.5.0.dist-info/licenses/LICENSE +21 -0
- polyrob-0.5.0.dist-info/top_level.txt +12 -0
- surfaces/README.md +70 -0
- surfaces/__init__.py +0 -0
- surfaces/email/__init__.py +0 -0
- surfaces/email/dedup.py +44 -0
- surfaces/email/harness.py +187 -0
- surfaces/email/inbound.py +159 -0
- surfaces/email/seed.py +60 -0
- surfaces/email/surface.py +105 -0
- surfaces/telegram/__init__.py +0 -0
- surfaces/telegram/dedup.py +83 -0
- surfaces/telegram/harness.py +886 -0
- surfaces/telegram/inbound.py +145 -0
- surfaces/telegram/interactive_tools.py +49 -0
- surfaces/telegram/markdown.py +347 -0
- surfaces/telegram/rate_limit.py +167 -0
- surfaces/telegram/surface.py +187 -0
- surfaces/telegram/voice.py +52 -0
- surfaces/whatsapp/__init__.py +0 -0
- surfaces/whatsapp/client.py +51 -0
- surfaces/whatsapp/harness.py +62 -0
- surfaces/whatsapp/inbound.py +132 -0
- surfaces/whatsapp/surface.py +64 -0
- surfaces/whatsapp/window.py +34 -0
- tools/README.md +578 -0
- tools/__init__.py +424 -0
- tools/alchemy/__init__.py +5 -0
- tools/alchemy/alchemy_tool.py +389 -0
- tools/anysite/__init__.py +17 -0
- tools/anysite/client.py +88 -0
- tools/anysite/tool.py +89 -0
- tools/base_tool.py +468 -0
- tools/browser/__init__.py +22 -0
- tools/browser/actions.py +118 -0
- tools/browser/browser.py +1457 -0
- tools/browser/browser_manager.py +690 -0
- tools/browser/context.py +1748 -0
- tools/browser/playwright_utils.py +137 -0
- tools/browser/views.py +56 -0
- tools/code_exec/SANDBOX_SECURITY.md +144 -0
- tools/code_exec/__init__.py +178 -0
- tools/code_exec/backend.py +73 -0
- tools/code_exec/backends/__init__.py +1 -0
- tools/code_exec/backends/docker.py +779 -0
- tools/code_exec/backends/local_subprocess.py +154 -0
- tools/code_exec/env_policy.py +45 -0
- tools/code_exec/result.py +42 -0
- tools/code_exec/sandbox_guard.py +59 -0
- tools/code_exec/tool.py +227 -0
- tools/coding/__init__.py +50 -0
- tools/coding/edit.py +107 -0
- tools/coding/search.py +113 -0
- tools/coding/tool.py +353 -0
- tools/collabland/__init__.py +5 -0
- tools/collabland/collabland_tool.py +471 -0
- tools/controller/__init__.py +0 -0
- tools/controller/_helpers.py +165 -0
- tools/controller/action_registration.py +1765 -0
- tools/controller/approval.py +244 -0
- tools/controller/approval_interactive.py +90 -0
- tools/controller/delegation.py +195 -0
- tools/controller/execution.py +797 -0
- tools/controller/execution_context.py +141 -0
- tools/controller/hooks.py +135 -0
- tools/controller/introspection.py +344 -0
- tools/controller/mcp_registrar.py +190 -0
- tools/controller/message_send.py +31 -0
- tools/controller/registry/__init__.py +4 -0
- tools/controller/registry/schema_generators.py +395 -0
- tools/controller/registry/schema_sanitizer.py +406 -0
- tools/controller/registry/service.py +1266 -0
- tools/controller/registry/views.py +151 -0
- tools/controller/service.py +409 -0
- tools/controller/tool_management.py +334 -0
- tools/controller/types.py +28 -0
- tools/controller/views.py +450 -0
- tools/cronjob_tools.py +166 -0
- tools/crypto_trade_gate.py +59 -0
- tools/descriptors.py +452 -0
- tools/dom/__init__.py +0 -0
- tools/dom/history_tree_processor/service.py +244 -0
- tools/dom/history_tree_processor/view.py +99 -0
- tools/dom/service.py +471 -0
- tools/dom/views.py +346 -0
- tools/email_tool.py +403 -0
- tools/exceptions.py +169 -0
- tools/filesystem.py +1038 -0
- tools/filesystem_docproc.py +331 -0
- tools/filesystem_pdf.py +755 -0
- tools/git/__init__.py +42 -0
- tools/git/tool.py +258 -0
- tools/github/__init__.py +33 -0
- tools/github/client.py +94 -0
- tools/github/tool.py +237 -0
- tools/goal_tools.py +309 -0
- tools/hyperliquid/__init__.py +56 -0
- tools/hyperliquid/models.py +233 -0
- tools/hyperliquid/service.py +1680 -0
- tools/knowledge_ingest.py +950 -0
- tools/mcp/README.md +535 -0
- tools/mcp/__init__.py +52 -0
- tools/mcp/catalog.py +115 -0
- tools/mcp/config.py +337 -0
- tools/mcp/mcp_tool.py +1415 -0
- tools/mcp/param_coercion.py +289 -0
- tools/mcp/protocol.py +1520 -0
- tools/mcp/rate_limit.py +50 -0
- tools/mcp/security.py +424 -0
- tools/mcp/self_install.py +111 -0
- tools/mcp/server_manager.py +829 -0
- tools/mcp/subscriptions.py +72 -0
- tools/mcp/user_mcp_service.py +899 -0
- tools/mcp/validation_tracker.py +89 -0
- tools/mcp/views.py +346 -0
- tools/oauth/__init__.py +17 -0
- tools/oauth/manager.py +75 -0
- tools/oauth/provider.py +69 -0
- tools/oauth/providers/__init__.py +1 -0
- tools/oauth/providers/generic_oauth2.py +96 -0
- tools/perplexity_tool.py +280 -0
- tools/polymarket/__init__.py +41 -0
- tools/polymarket/clob_adapter.py +76 -0
- tools/polymarket/models.py +182 -0
- tools/polymarket/service.py +2082 -0
- tools/self_env/__init__.py +61 -0
- tools/self_env/tool.py +274 -0
- tools/shell/__init__.py +80 -0
- tools/shell/backend_pool.py +118 -0
- tools/shell/discipline.py +62 -0
- tools/shell/executor.py +157 -0
- tools/shell/loopback_allow.py +87 -0
- tools/shell/process_registry.py +113 -0
- tools/shell/process_tool.py +131 -0
- tools/shell/state.py +129 -0
- tools/shell/tool.py +151 -0
- tools/task_tool.py +637 -0
- tools/twitter_tool.py +2086 -0
- tools/user_directory.py +179 -0
- tools/web_fetch/__init__.py +3 -0
- tools/web_fetch/fetcher.py +144 -0
- tools/web_fetch/render.py +44 -0
- tools/web_fetch/tool.py +60 -0
- tools/x402/__init__.py +60 -0
- tools/x402/client.py +47 -0
- tools/x402/invoice_tool.py +143 -0
- tools/x402/real_client.py +250 -0
- tools/x402/service.py +131 -0
- utils/README.md +498 -0
- utils/__init__.py +46 -0
- utils/auth_utils.py +114 -0
- utils/bounded_collections.py +162 -0
- utils/circuit_breaker.py +389 -0
- utils/gif_utils.py +435 -0
- utils/markdown_utils.py +291 -0
- utils/message_utils.py +147 -0
- utils/metrics.py +36 -0
- utils/path_validator.py +227 -0
- utils/rate_limit_manager.py +399 -0
- utils/result_size.py +296 -0
- utils/time_utils.py +206 -0
- utils/user_utils.py +188 -0
- webview/README.md +111 -0
- webview/RENAME_AND_ALIGN_HANDOFF.md +200 -0
- webview/__init__.py +8 -0
- webview/activity.py +636 -0
- webview/owner_auth.py +108 -0
- webview/pages.py +448 -0
- webview/repair_sessions.py +399 -0
- webview/server.py +4323 -0
- webview/server_launcher.py +112 -0
- webview/static/css/activity.css +89 -0
- webview/static/css/chat.css +1772 -0
- webview/static/css/components.css +1190 -0
- webview/static/css/config-panel.css +651 -0
- webview/static/css/pages/admin.css +766 -0
- webview/static/css/pages/profile.css +231 -0
- webview/static/css/pages/settings.css +297 -0
- webview/static/css/pages/signin.css +316 -0
- webview/static/css/style.css +4250 -0
- webview/static/css/variables.css +147 -0
- webview/static/css/workspace-fullwidth-fix.css +109 -0
- webview/static/img/favicon.ico +0 -0
- webview/static/js/activity.js +394 -0
- webview/static/js/admin/activity.js +186 -0
- webview/static/js/admin/dashboard.js +163 -0
- webview/static/js/admin/user_detail.js +400 -0
- webview/static/js/admin/users.js +174 -0
- webview/static/js/admin/utils.js +214 -0
- webview/static/js/chat.js +3178 -0
- webview/static/js/config-panel.js +522 -0
- webview/static/js/constants.js +20 -0
- webview/static/js/error-handler.js +226 -0
- webview/static/js/ethers.min.js +1 -0
- webview/static/js/event-filter.js +137 -0
- webview/static/js/event-store.js +351 -0
- webview/static/js/file-attachments.js +374 -0
- webview/static/js/file-loader.js +163 -0
- webview/static/js/index.js +103 -0
- webview/static/js/performance-utils.js +196 -0
- webview/static/js/profile.js +396 -0
- webview/static/js/screenshot.js +386 -0
- webview/static/js/session.js +1527 -0
- webview/static/js/settings.js +1288 -0
- webview/static/js/sidebar-data.js +766 -0
- webview/static/js/sidebar-toggle.js +296 -0
- webview/static/js/socket.io.min.js +7 -0
- webview/static/js/stats.js +355 -0
- webview/static/js/ui-utils.js +420 -0
- webview/static/js/workspace.js +966 -0
- webview/stats_service.py +613 -0
- webview/templates/__init__.py +1 -0
- webview/templates/__pycache__/__init__.cpython-311.pyc +0 -0
- webview/templates/activity.html +36 -0
- webview/templates/admin/activity.html +122 -0
- webview/templates/admin/dashboard.html +112 -0
- webview/templates/admin/user_detail.html +228 -0
- webview/templates/admin/users.html +105 -0
- webview/templates/autonomy.html +60 -0
- webview/templates/error.html +133 -0
- webview/templates/finance.html +90 -0
- webview/templates/identity.html +73 -0
- webview/templates/index.html +42 -0
- webview/templates/layout.html +125 -0
- webview/templates/memory.html +59 -0
- webview/templates/owner_login.html +15 -0
- webview/templates/profile.html +121 -0
- webview/templates/session.html +312 -0
- webview/templates/settings.html +534 -0
- webview/templates/sidebar.html +112 -0
- webview/templates/signin.html +213 -0
- webview/templates/status.html +14 -0
- webview/templates/system.html +45 -0
- webview/webgate.py +196 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Session registry — maps session_id -> SessionOrchestrator.
|
|
2
|
+
|
|
3
|
+
This is the single seam through which the active in-memory orchestrators are
|
|
4
|
+
looked up and mutated. Today it wraps a plain in-process dict, which is what
|
|
5
|
+
forces ``UVICORN_WORKERS=1`` in production (an orchestrator created in one
|
|
6
|
+
worker is invisible to another). Keeping every access behind this interface
|
|
7
|
+
means the future swap to a cross-process, SQLite-backed registry (WAL mode,
|
|
8
|
+
jittered retry — see ``docs/ROB_CORE_SERVER_SPLIT_SPEC.md`` Phase E) is
|
|
9
|
+
mechanical rather than a scavenger hunt through ``task_agent_lite``.
|
|
10
|
+
|
|
11
|
+
The interface is intentionally small and dict-flavoured so callers read
|
|
12
|
+
naturally:
|
|
13
|
+
|
|
14
|
+
registry.register(session_id, orchestrator)
|
|
15
|
+
orch = registry.get(session_id)
|
|
16
|
+
orch = registry.remove(session_id) # pop, returns None if absent
|
|
17
|
+
if session_id in registry: ...
|
|
18
|
+
for sid, orch in registry.items(): ...
|
|
19
|
+
len(registry)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SessionRegistry:
|
|
28
|
+
"""In-process registry of active session orchestrators.
|
|
29
|
+
|
|
30
|
+
Not thread/process shared. See module docstring for the scaling caveat.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self) -> None:
|
|
34
|
+
self._orchestrators: Dict[str, Any] = {}
|
|
35
|
+
|
|
36
|
+
def register(self, session_id: str, orchestrator: Any, *, params: Optional[Dict[str, Any]] = None) -> None:
|
|
37
|
+
"""Store (or replace) the orchestrator for ``session_id``.
|
|
38
|
+
|
|
39
|
+
``params`` is accepted (and ignored) for signature parity with the
|
|
40
|
+
SQLite-backed registry, so callers can pass it without crashing whichever
|
|
41
|
+
backend ``SESSION_REGISTRY_BACKEND`` selected.
|
|
42
|
+
"""
|
|
43
|
+
self._orchestrators[session_id] = orchestrator
|
|
44
|
+
|
|
45
|
+
def get(self, session_id: str) -> Optional[Any]:
|
|
46
|
+
"""Return the orchestrator for ``session_id`` or ``None``."""
|
|
47
|
+
return self._orchestrators.get(session_id)
|
|
48
|
+
|
|
49
|
+
def route(self, session_id: str):
|
|
50
|
+
"""Routing decision (P6). In-process registry knows only local sessions, so
|
|
51
|
+
the result is either LOCAL (handle here) or MISSING."""
|
|
52
|
+
from agents.task.session_route import SessionRoute, LOCAL, MISSING
|
|
53
|
+
orch = self._orchestrators.get(session_id)
|
|
54
|
+
if orch is not None:
|
|
55
|
+
return SessionRoute(status=LOCAL, orchestrator=orch)
|
|
56
|
+
return SessionRoute(status=MISSING)
|
|
57
|
+
|
|
58
|
+
def remove(self, session_id: str) -> Optional[Any]:
|
|
59
|
+
"""Remove and return the orchestrator for ``session_id`` (``None`` if absent)."""
|
|
60
|
+
return self._orchestrators.pop(session_id, None)
|
|
61
|
+
|
|
62
|
+
def contains(self, session_id: str) -> bool:
|
|
63
|
+
return session_id in self._orchestrators
|
|
64
|
+
|
|
65
|
+
def count(self) -> int:
|
|
66
|
+
return len(self._orchestrators)
|
|
67
|
+
|
|
68
|
+
def items(self) -> List[Tuple[str, Any]]:
|
|
69
|
+
"""Snapshot of (session_id, orchestrator) pairs — safe to mutate while iterating."""
|
|
70
|
+
return list(self._orchestrators.items())
|
|
71
|
+
|
|
72
|
+
def values(self) -> List[Any]:
|
|
73
|
+
"""Snapshot of the registered orchestrators."""
|
|
74
|
+
return list(self._orchestrators.values())
|
|
75
|
+
|
|
76
|
+
def session_ids(self) -> List[str]:
|
|
77
|
+
return list(self._orchestrators.keys())
|
|
78
|
+
|
|
79
|
+
def clear(self) -> None:
|
|
80
|
+
self._orchestrators.clear()
|
|
81
|
+
|
|
82
|
+
# --- dict-flavoured conveniences ---
|
|
83
|
+
|
|
84
|
+
def __contains__(self, session_id: str) -> bool:
|
|
85
|
+
return session_id in self._orchestrators
|
|
86
|
+
|
|
87
|
+
def __len__(self) -> int:
|
|
88
|
+
return len(self._orchestrators)
|
|
89
|
+
|
|
90
|
+
def __iter__(self) -> Iterator[str]:
|
|
91
|
+
return iter(list(self._orchestrators.keys()))
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Cross-worker session routing result (roadmap P6).
|
|
2
|
+
|
|
3
|
+
The routing *decision* a worker makes for an incoming session request: handle it
|
|
4
|
+
locally, report which worker owns it (so the caller can forward / return a
|
|
5
|
+
meaningful 409 instead of a false 404), or report it missing. Built on the
|
|
6
|
+
SQLite registry's cross-process visibility (``exists``/``owner_pid``); actual
|
|
7
|
+
request forwarding (HTTP/IPC proxy) is a thin layer on top of this decision.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any, Optional
|
|
13
|
+
|
|
14
|
+
LOCAL = "local" # owned by this worker; orchestrator is in-process
|
|
15
|
+
REMOTE = "remote" # exists but owned by another worker (owner_pid set)
|
|
16
|
+
MISSING = "missing" # no such active session anywhere
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class SessionRoute:
|
|
21
|
+
status: str # LOCAL | REMOTE | MISSING
|
|
22
|
+
orchestrator: Optional[Any] = None # set only when LOCAL
|
|
23
|
+
owner_pid: Optional[int] = None # set for LOCAL and REMOTE
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def is_local(self) -> bool:
|
|
27
|
+
return self.status == LOCAL
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def is_remote(self) -> bool:
|
|
31
|
+
return self.status == REMOTE
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def is_missing(self) -> bool:
|
|
35
|
+
return self.status == MISSING
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""SQLite-backed session registry (roadmap P6 / §33 — the scale unlock).
|
|
2
|
+
|
|
3
|
+
The in-process :class:`SessionRegistry` forces ``UVICORN_WORKERS=1``: an
|
|
4
|
+
orchestrator created in one worker is invisible to another, so a request that
|
|
5
|
+
lands on the wrong worker 404s. This variant keeps the **local object dict**
|
|
6
|
+
(the live orchestrator can't cross processes) but ALSO mirrors session metadata to
|
|
7
|
+
a shared SQLite ``active_sessions`` table (WAL + jittered retry) so every worker
|
|
8
|
+
can see which sessions exist and who owns them — the routing foundation for
|
|
9
|
+
``workers>1``.
|
|
10
|
+
|
|
11
|
+
Drop-in compatible with ``SessionRegistry`` (register/get/remove/contains/count/…)
|
|
12
|
+
plus cross-process methods: ``exists``, ``owner_pid``, ``global_session_ids``,
|
|
13
|
+
``global_count``, ``heartbeat``, ``reap_stale``.
|
|
14
|
+
|
|
15
|
+
Scope note: this gives cross-worker *visibility*, not transparent cross-worker
|
|
16
|
+
*method calls* on a remote orchestrator (that needs sticky routing / IPC — a
|
|
17
|
+
follow-up). With it, a worker can return a meaningful "owned by pid N / elsewhere"
|
|
18
|
+
result instead of a false 404.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import uuid
|
|
25
|
+
from datetime import datetime, timedelta
|
|
26
|
+
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
|
27
|
+
|
|
28
|
+
from core.sqlite_util import execute_retry
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SqliteSessionRegistry:
|
|
32
|
+
def __init__(self, db_path: str, *, worker_pid: Optional[int] = None) -> None:
|
|
33
|
+
self.db_path = db_path
|
|
34
|
+
self._pid = worker_pid or os.getpid()
|
|
35
|
+
# Per-process boot id: ownership is keyed on this, NOT the (reusable) pid.
|
|
36
|
+
# Two processes that happen to share a pid (PID reuse after a worker dies)
|
|
37
|
+
# still get distinct boot ids, so routing never mistakes one for the other.
|
|
38
|
+
self._boot_id = uuid.uuid4().hex
|
|
39
|
+
parent = os.path.dirname(db_path)
|
|
40
|
+
if parent:
|
|
41
|
+
os.makedirs(parent, exist_ok=True)
|
|
42
|
+
self._orchestrators: Dict[str, Any] = {}
|
|
43
|
+
self._init_schema()
|
|
44
|
+
|
|
45
|
+
def _init_schema(self) -> None:
|
|
46
|
+
# owner_boot_id is in the CREATE so a FRESH DB never needs the ALTER — this
|
|
47
|
+
# avoids the concurrent-boot race where two workers both PRAGMA-see the column
|
|
48
|
+
# missing and both ALTER (the second crashing with "duplicate column name").
|
|
49
|
+
execute_retry(
|
|
50
|
+
self.db_path,
|
|
51
|
+
"""
|
|
52
|
+
CREATE TABLE IF NOT EXISTS active_sessions (
|
|
53
|
+
session_id TEXT PRIMARY KEY,
|
|
54
|
+
worker_pid INTEGER NOT NULL,
|
|
55
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
56
|
+
params TEXT NOT NULL DEFAULT '{}',
|
|
57
|
+
last_seen_at TEXT NOT NULL,
|
|
58
|
+
created_at TEXT NOT NULL,
|
|
59
|
+
owner_boot_id TEXT
|
|
60
|
+
)
|
|
61
|
+
""",
|
|
62
|
+
)
|
|
63
|
+
# Migration for pre-existing DBs created before owner_boot_id. (No
|
|
64
|
+
# ALTER ... IF NOT EXISTS in SQLite.) Tolerate the "duplicate column name"
|
|
65
|
+
# error so a concurrent boot that adds it first can't crash this worker.
|
|
66
|
+
cols = execute_retry(
|
|
67
|
+
self.db_path, "PRAGMA table_info(active_sessions)", fetch="all",
|
|
68
|
+
) or []
|
|
69
|
+
names = {row["name"] for row in cols}
|
|
70
|
+
if "owner_boot_id" not in names:
|
|
71
|
+
try:
|
|
72
|
+
execute_retry(
|
|
73
|
+
self.db_path,
|
|
74
|
+
"ALTER TABLE active_sessions ADD COLUMN owner_boot_id TEXT",
|
|
75
|
+
)
|
|
76
|
+
except Exception as e: # concurrent add won the race — that's fine
|
|
77
|
+
if "duplicate column" not in str(e).lower():
|
|
78
|
+
raise
|
|
79
|
+
|
|
80
|
+
# --- local object dict (compatible with SessionRegistry) -----------------
|
|
81
|
+
|
|
82
|
+
def register(self, session_id: str, orchestrator: Any, *, params: Optional[Dict[str, Any]] = None) -> None:
|
|
83
|
+
# Write the DB row BEFORE the local dict. route() reads the DB; if the DB
|
|
84
|
+
# write raised after we'd already stored the object locally, route() would
|
|
85
|
+
# return MISSING for a session this worker actually holds — a false 404, the
|
|
86
|
+
# exact failure this class exists to prevent.
|
|
87
|
+
now = datetime.now().isoformat()
|
|
88
|
+
execute_retry(
|
|
89
|
+
self.db_path,
|
|
90
|
+
"""
|
|
91
|
+
INSERT INTO active_sessions
|
|
92
|
+
(session_id, worker_pid, owner_boot_id, status, params, last_seen_at, created_at)
|
|
93
|
+
VALUES (?,?,?,?,?,?,?)
|
|
94
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
95
|
+
worker_pid=excluded.worker_pid, owner_boot_id=excluded.owner_boot_id,
|
|
96
|
+
status='active', params=excluded.params, last_seen_at=excluded.last_seen_at
|
|
97
|
+
""",
|
|
98
|
+
(session_id, self._pid, self._boot_id, "active", json.dumps(params or {}), now, now),
|
|
99
|
+
)
|
|
100
|
+
self._orchestrators[session_id] = orchestrator
|
|
101
|
+
|
|
102
|
+
def get(self, session_id: str) -> Optional[Any]:
|
|
103
|
+
"""Return the LOCAL orchestrator object (None if owned by another worker)."""
|
|
104
|
+
return self._orchestrators.get(session_id)
|
|
105
|
+
|
|
106
|
+
def remove(self, session_id: str) -> Optional[Any]:
|
|
107
|
+
execute_retry(self.db_path, "DELETE FROM active_sessions WHERE session_id=?", (session_id,))
|
|
108
|
+
return self._orchestrators.pop(session_id, None)
|
|
109
|
+
|
|
110
|
+
def contains(self, session_id: str) -> bool:
|
|
111
|
+
return session_id in self._orchestrators
|
|
112
|
+
|
|
113
|
+
def count(self) -> int:
|
|
114
|
+
return len(self._orchestrators)
|
|
115
|
+
|
|
116
|
+
def items(self) -> List[Tuple[str, Any]]:
|
|
117
|
+
return list(self._orchestrators.items())
|
|
118
|
+
|
|
119
|
+
def values(self) -> List[Any]:
|
|
120
|
+
return list(self._orchestrators.values())
|
|
121
|
+
|
|
122
|
+
def session_ids(self) -> List[str]:
|
|
123
|
+
return list(self._orchestrators.keys())
|
|
124
|
+
|
|
125
|
+
def clear(self) -> None:
|
|
126
|
+
for sid in list(self._orchestrators):
|
|
127
|
+
execute_retry(self.db_path, "DELETE FROM active_sessions WHERE session_id=?", (sid,))
|
|
128
|
+
self._orchestrators.clear()
|
|
129
|
+
|
|
130
|
+
# --- cross-process metadata (SQLite) -------------------------------------
|
|
131
|
+
|
|
132
|
+
def exists(self, session_id: str) -> bool:
|
|
133
|
+
row = execute_retry(
|
|
134
|
+
self.db_path, "SELECT 1 FROM active_sessions WHERE session_id=?", (session_id,), fetch="one",
|
|
135
|
+
)
|
|
136
|
+
return row is not None
|
|
137
|
+
|
|
138
|
+
def route(self, session_id: str):
|
|
139
|
+
"""Routing decision (P6): LOCAL (object is in this worker), REMOTE (exists but
|
|
140
|
+
owned by another worker — caller can forward / 409 with owner_pid), or MISSING."""
|
|
141
|
+
from agents.task.session_route import SessionRoute, LOCAL, REMOTE, MISSING
|
|
142
|
+
row = execute_retry(
|
|
143
|
+
self.db_path,
|
|
144
|
+
"SELECT worker_pid, owner_boot_id FROM active_sessions WHERE session_id=?",
|
|
145
|
+
(session_id,), fetch="one",
|
|
146
|
+
)
|
|
147
|
+
if row is None:
|
|
148
|
+
return SessionRoute(status=MISSING)
|
|
149
|
+
owner = int(row["worker_pid"])
|
|
150
|
+
owner_boot = row["owner_boot_id"]
|
|
151
|
+
# LOCAL only when the OWNING boot id is ours (pid match alone is not enough:
|
|
152
|
+
# a reused pid in a fresh process must not be treated as the same owner).
|
|
153
|
+
local = self._orchestrators.get(session_id)
|
|
154
|
+
if owner_boot == self._boot_id and local is not None:
|
|
155
|
+
return SessionRoute(status=LOCAL, orchestrator=local, owner_pid=owner)
|
|
156
|
+
return SessionRoute(status=REMOTE, owner_pid=owner)
|
|
157
|
+
|
|
158
|
+
def owner_pid(self, session_id: str) -> Optional[int]:
|
|
159
|
+
row = execute_retry(
|
|
160
|
+
self.db_path, "SELECT worker_pid FROM active_sessions WHERE session_id=?", (session_id,), fetch="one",
|
|
161
|
+
)
|
|
162
|
+
return int(row["worker_pid"]) if row else None
|
|
163
|
+
|
|
164
|
+
def owner_boot_id(self, session_id: str) -> Optional[str]:
|
|
165
|
+
row = execute_retry(
|
|
166
|
+
self.db_path, "SELECT owner_boot_id FROM active_sessions WHERE session_id=?", (session_id,), fetch="one",
|
|
167
|
+
)
|
|
168
|
+
return row["owner_boot_id"] if row else None
|
|
169
|
+
|
|
170
|
+
def global_session_ids(self) -> List[str]:
|
|
171
|
+
rows = execute_retry(self.db_path, "SELECT session_id FROM active_sessions", fetch="all")
|
|
172
|
+
return [r["session_id"] for r in rows]
|
|
173
|
+
|
|
174
|
+
def global_count(self) -> int:
|
|
175
|
+
row = execute_retry(self.db_path, "SELECT COUNT(*) AS c FROM active_sessions", fetch="one")
|
|
176
|
+
return int(row["c"]) if row else 0
|
|
177
|
+
|
|
178
|
+
def heartbeat(self, session_id: str) -> None:
|
|
179
|
+
execute_retry(
|
|
180
|
+
self.db_path, "UPDATE active_sessions SET last_seen_at=? WHERE session_id=?",
|
|
181
|
+
(datetime.now().isoformat(), session_id),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
def _pid_alive(pid: Optional[int]) -> bool:
|
|
186
|
+
"""True if ``pid`` is a live process on THIS host.
|
|
187
|
+
|
|
188
|
+
Same-host multi-worker (uvicorn workers) is the supported ``workers>1``
|
|
189
|
+
deployment, so a signal-0 liveness probe is meaningful. Be conservative:
|
|
190
|
+
an unknown/None pid or any unexpected error is treated as ALIVE so we never
|
|
191
|
+
reap a possibly-live owner. (A cross-host DB would need a heartbeat-based
|
|
192
|
+
liveness signal; that deployment is out of scope for the live orchestrator.)
|
|
193
|
+
"""
|
|
194
|
+
if not pid or pid <= 0:
|
|
195
|
+
return False
|
|
196
|
+
try:
|
|
197
|
+
os.kill(pid, 0)
|
|
198
|
+
return True
|
|
199
|
+
except ProcessLookupError:
|
|
200
|
+
return False
|
|
201
|
+
except PermissionError:
|
|
202
|
+
return True # exists but owned by another user
|
|
203
|
+
except Exception:
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
def reap_stale(self, ttl_seconds: int = 300) -> List[str]:
|
|
207
|
+
"""Remove sessions whose last_seen_at is older than ttl. Returns reaped ids.
|
|
208
|
+
|
|
209
|
+
A row is reaped ONLY if it is truly abandoned: stale AND not held locally AND
|
|
210
|
+
its owning worker process is DEAD. A session sitting IDLE between user turns
|
|
211
|
+
does not step, so its heartbeat freezes — but it is still alive in its owning
|
|
212
|
+
worker and must never be reaped (deleting the row would force a false-404 and
|
|
213
|
+
leak that worker's orchestrator). Rows we hold locally, and rows whose
|
|
214
|
+
``worker_pid`` is still alive on this host, are skipped; only genuinely
|
|
215
|
+
dead-worker rows are cleaned up. Complements the per-step heartbeat.
|
|
216
|
+
"""
|
|
217
|
+
cutoff = (datetime.now() - timedelta(seconds=ttl_seconds)).isoformat()
|
|
218
|
+
# Sessions alive in THIS process, regardless of heartbeat staleness.
|
|
219
|
+
local_sids = set(self._orchestrators.keys())
|
|
220
|
+
rows = execute_retry(
|
|
221
|
+
self.db_path,
|
|
222
|
+
"SELECT session_id, worker_pid FROM active_sessions WHERE last_seen_at < ?",
|
|
223
|
+
(cutoff,), fetch="all",
|
|
224
|
+
)
|
|
225
|
+
# Reap only rows that are (a) not ours and (b) owned by a DEAD process. A
|
|
226
|
+
# stale row whose owner worker is still alive is an idle session on another
|
|
227
|
+
# worker — deleting it would false-404 and orphan that worker's orchestrator.
|
|
228
|
+
reaped = [
|
|
229
|
+
r["session_id"] for r in rows
|
|
230
|
+
if r["session_id"] not in local_sids and not self._pid_alive(r["worker_pid"])
|
|
231
|
+
]
|
|
232
|
+
for sid in reaped:
|
|
233
|
+
# Re-check under the cutoff: don't delete a row that heartbeated between
|
|
234
|
+
# the SELECT and the DELETE (and re-skip if it somehow became local).
|
|
235
|
+
if sid in self._orchestrators:
|
|
236
|
+
continue
|
|
237
|
+
execute_retry(
|
|
238
|
+
self.db_path,
|
|
239
|
+
"DELETE FROM active_sessions WHERE session_id=? AND last_seen_at < ?",
|
|
240
|
+
(sid, cutoff),
|
|
241
|
+
)
|
|
242
|
+
self._orchestrators.pop(sid, None)
|
|
243
|
+
return reaped
|
|
244
|
+
|
|
245
|
+
# test helper: force a row's last_seen_at (e.g. into the past) for reap tests.
|
|
246
|
+
def _set_last_seen(self, session_id: str, iso: str) -> None:
|
|
247
|
+
execute_retry(self.db_path, "UPDATE active_sessions SET last_seen_at=? WHERE session_id=?", (iso, session_id))
|
|
248
|
+
|
|
249
|
+
# backwards-compatible alias
|
|
250
|
+
_set_last_seen_for_test = _set_last_seen
|
|
251
|
+
|
|
252
|
+
# --- dict-flavoured conveniences -----------------------------------------
|
|
253
|
+
|
|
254
|
+
def __contains__(self, session_id: str) -> bool:
|
|
255
|
+
return session_id in self._orchestrators
|
|
256
|
+
|
|
257
|
+
def __len__(self) -> int:
|
|
258
|
+
return len(self._orchestrators)
|
|
259
|
+
|
|
260
|
+
def __iter__(self) -> Iterator[str]:
|
|
261
|
+
return iter(list(self._orchestrators.keys()))
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Feature flags for the Singular Chat Interface (Surface contract).
|
|
2
|
+
|
|
3
|
+
Read through this class (NOT BotConfig.get, which is getattr-backed and returns
|
|
4
|
+
the default for any unknown key -> a gate read that way is permanently off).
|
|
5
|
+
Reuses constants._bool_env so default-ON and default-OFF flags share one parser.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from agents.task.constants import _bool_env, local_mode_enabled
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _int_env(name: str, default: int) -> int:
|
|
14
|
+
try:
|
|
15
|
+
v = os.getenv(name)
|
|
16
|
+
return int(v) if v is not None and v.strip() != "" else default
|
|
17
|
+
except (TypeError, ValueError):
|
|
18
|
+
return default
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SurfaceConfig:
|
|
22
|
+
@staticmethod
|
|
23
|
+
def singular_chat_enabled() -> bool:
|
|
24
|
+
return _bool_env("SINGULAR_CHAT_ENABLED", False)
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def telegram_surface_enabled() -> bool:
|
|
28
|
+
return _bool_env("TELEGRAM_SURFACE_ENABLED", False)
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def whatsapp_surface_enabled() -> bool:
|
|
32
|
+
return _bool_env("WHATSAPP_SURFACE_ENABLED", False)
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def email_surface_enabled() -> bool:
|
|
36
|
+
"""Run the email surface (IMAP poll inbound + SMTP outbound). Default OFF.
|
|
37
|
+
v1 is correspondent-only — owner-by-email stays OFF until verified-sender lands."""
|
|
38
|
+
return _bool_env("EMAIL_SURFACE_ENABLED", False)
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def email_imap_poll_sec() -> int:
|
|
42
|
+
"""Seconds between IMAP polls for new mail (no IDLE in v1)."""
|
|
43
|
+
return _int_env("EMAIL_IMAP_POLL_SEC", 60)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def chat_intent_classifier_enabled() -> bool:
|
|
47
|
+
return _bool_env("CHAT_INTENT_CLASSIFIER", False)
|
|
48
|
+
|
|
49
|
+
# --- WS-A three-tier access model (owner/correspondent/denied) -----------
|
|
50
|
+
@staticmethod
|
|
51
|
+
def correspondent_access_enabled() -> bool:
|
|
52
|
+
"""Resolve every inbound to an access tier (OWNER/CORRESPONDENT/DENIED) at the
|
|
53
|
+
routing boundary. Default OFF — when off, route_inbound is byte-identical to
|
|
54
|
+
today (only the legacy pairing gate applies). When on, a non-owner is routable
|
|
55
|
+
only as a known correspondent (its reply is DATA delivered to the originating
|
|
56
|
+
session, never a steering turn)."""
|
|
57
|
+
return _bool_env("CORRESPONDENT_ACCESS_ENABLED", False)
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def correspondent_require_approval() -> bool:
|
|
61
|
+
"""A newly auto-seeded correspondent is PENDING (owner must approve) before its
|
|
62
|
+
replies are routable. Default ON — a third party the agent contacted does not
|
|
63
|
+
become a trusted-data channel until the owner ratifies it. Set
|
|
64
|
+
CORRESPONDENT_REQUIRE_APPROVAL=false for single-user/local convenience."""
|
|
65
|
+
return _bool_env("CORRESPONDENT_REQUIRE_APPROVAL", True)
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def correspondent_max_new_per_day() -> int:
|
|
69
|
+
"""Per-tenant cap on NEW correspondents seeded per 24h — bounds the blast radius
|
|
70
|
+
of an injection that tricks the agent into mass-contacting addresses."""
|
|
71
|
+
return _int_env("CORRESPONDENT_MAX_NEW_PER_DAY", 20)
|
|
72
|
+
|
|
73
|
+
# --- #9 voice transcription ---------------------------------------------
|
|
74
|
+
@staticmethod
|
|
75
|
+
def voice_transcription_enabled() -> bool:
|
|
76
|
+
"""Transcribe inbound voice/audio messages to text before routing (#9).
|
|
77
|
+
Default ON: when the faster-whisper extra is installed, voice notes are
|
|
78
|
+
transcribed; when it isn't, the surface degrades gracefully (the inbound guard
|
|
79
|
+
tells the user voice is unavailable instead of routing an empty turn).
|
|
80
|
+
Set VOICE_TRANSCRIPTION_ENABLED=false to turn it off."""
|
|
81
|
+
return _bool_env("VOICE_TRANSCRIPTION_ENABLED", True)
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def voice_transcription_model() -> str:
|
|
85
|
+
"""faster-whisper model size: tiny|base|small|medium|large-v3. `base` balances
|
|
86
|
+
CPU latency vs accuracy for short voice notes."""
|
|
87
|
+
return (os.getenv("VOICE_TRANSCRIPTION_MODEL") or "base").strip()
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def voice_transcription_required() -> bool:
|
|
91
|
+
"""When ON, a missing faster-whisper engine is a startup ERROR (not just WARN).
|
|
92
|
+
Default OFF: most deploys degrade gracefully (voice refused with a notice)."""
|
|
93
|
+
return _bool_env("VOICE_TRANSCRIPTION_REQUIRED", False)
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def voice_transcript_echo_enabled() -> bool:
|
|
97
|
+
"""Echo the transcript back into the chat as a persistent, voice-note-anchored
|
|
98
|
+
message before the agent answers (Telegram + WhatsApp). Default ON; purely additive.
|
|
99
|
+
Set VOICE_TRANSCRIPT_ECHO=false for byte-identical prior behavior."""
|
|
100
|
+
return _bool_env("VOICE_TRANSCRIPT_ECHO", True)
|
|
101
|
+
|
|
102
|
+
# --- #8 Telegram incremental streaming ----------------------------------
|
|
103
|
+
@staticmethod
|
|
104
|
+
def telegram_incremental_stream() -> bool:
|
|
105
|
+
"""Live `editMessageText` streaming on Telegram (#8). Default OFF — the buffered
|
|
106
|
+
one-send-on-finalize path stays the safe default; opt in per deployment."""
|
|
107
|
+
return _bool_env("TELEGRAM_INCREMENTAL_STREAM", False)
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def telegram_stream_edit_interval_sec() -> float:
|
|
111
|
+
"""Minimum seconds between live stream edits (flood-control). Telegram tolerates
|
|
112
|
+
only a few edits/sec; 1.5s is conservative. `0` edits on every delta (tests)."""
|
|
113
|
+
try:
|
|
114
|
+
v = os.getenv("TELEGRAM_STREAM_EDIT_INTERVAL_SEC")
|
|
115
|
+
return float(v) if v is not None and v.strip() != "" else 1.5
|
|
116
|
+
except (TypeError, ValueError):
|
|
117
|
+
return 1.5
|
|
118
|
+
|
|
119
|
+
# --- P0.1 session-boundary policy ---------------------------------------
|
|
120
|
+
@staticmethod
|
|
121
|
+
def session_reset_mode() -> str:
|
|
122
|
+
"""idle | daily | both | none. Default `idle` everywhere (#7): a fresh thread
|
|
123
|
+
after a cooldown is natural and memory recall bridges the gap. The server flip
|
|
124
|
+
(none->idle) is gated on the recreate-race (#2) + mute-on-resume (#0) fixes,
|
|
125
|
+
both landed, so a reset can't drop a reply or double-build an orchestrator.
|
|
126
|
+
Pin `SESSION_RESET_MODE=none` to restore the legacy inert behavior; `daily` is
|
|
127
|
+
opt-in (server-local-tz skew). The idle WINDOW still differs by profile
|
|
128
|
+
(`session_idle_minutes`: 720 local / 1440 server)."""
|
|
129
|
+
return (os.getenv("SESSION_RESET_MODE") or "idle").strip().lower()
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def session_idle_minutes() -> int:
|
|
133
|
+
"""Idle threshold; a chat older than this since last activity starts fresh.
|
|
134
|
+
Default 1440 (24h); 720 (12h) under POLYROB_LOCAL (a personal bot rolls sooner)."""
|
|
135
|
+
return _int_env("SESSION_IDLE_MINUTES", 720 if local_mode_enabled() else 1440)
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def session_reset_hour() -> int:
|
|
139
|
+
"""Local hour (0-23) for the daily session roll."""
|
|
140
|
+
return _int_env("SESSION_RESET_HOUR", 4)
|
|
141
|
+
|
|
142
|
+
# --- outbound durability -------------------------------------------------
|
|
143
|
+
@staticmethod
|
|
144
|
+
def outbound_queue_enabled() -> bool:
|
|
145
|
+
"""Route final outbound messages through the durable OutboundDeliveryQueue
|
|
146
|
+
(retry + dead-letter). Default OFF -> publish() sends directly (legacy). Streaming
|
|
147
|
+
deltas are always best-effort (never queued)."""
|
|
148
|
+
return _bool_env("OUTBOUND_QUEUE_ENABLED", False)
|
|
149
|
+
|
|
150
|
+
# --- a5 surface GC -------------------------------------------------------
|
|
151
|
+
@staticmethod
|
|
152
|
+
def surface_gc_enabled() -> bool:
|
|
153
|
+
"""Periodic GC of stale chat<->session bindings (a5). Default ON under the local
|
|
154
|
+
profile when the chat bus is on; OFF on the multi-tenant server until validated."""
|
|
155
|
+
return _bool_env(
|
|
156
|
+
"SURFACE_GC_ENABLED",
|
|
157
|
+
local_mode_enabled() and SurfaceConfig.singular_chat_enabled(),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def surface_gc_horizon_secs() -> int:
|
|
162
|
+
"""Bindings with no activity for longer than this are purged. ``max(2x idle
|
|
163
|
+
window, 7d)`` so GC never races the idle-reset boundary — a pointer is only
|
|
164
|
+
dropped well after it would already start a fresh thread."""
|
|
165
|
+
return max(SurfaceConfig.session_idle_minutes() * 60 * 2, 7 * 86400)
|
|
166
|
+
|
|
167
|
+
# --- webhook signing secrets ------------------------------------------------
|
|
168
|
+
@staticmethod
|
|
169
|
+
def whatsapp_template_name() -> str:
|
|
170
|
+
"""Approved utility template used to re-open the 24h window for a proactive msg."""
|
|
171
|
+
return (os.getenv("WHATSAPP_TEMPLATE_NAME") or "task_ready").strip()
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
def webhook_secret(surface_id: str) -> "Optional[str]":
|
|
175
|
+
"""Read {SURFACE}_WEBHOOK_SECRET from env. Surface id is upper-cased:
|
|
176
|
+
whatsapp -> WHATSAPP_WEBHOOK_SECRET. Returns stripped value or None."""
|
|
177
|
+
v = os.getenv(f"{surface_id.upper()}_WEBHOOK_SECRET")
|
|
178
|
+
return v.strip() if v and v.strip() else None
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def webhook_verify_token(surface_id: str) -> "Optional[str]":
|
|
182
|
+
"""Read {SURFACE}_VERIFY_TOKEN from env. Surface id is upper-cased:
|
|
183
|
+
whatsapp -> WHATSAPP_VERIFY_TOKEN. Returns stripped value or None."""
|
|
184
|
+
v = os.getenv(f"{surface_id.upper()}_VERIFY_TOKEN")
|
|
185
|
+
return v.strip() if v and v.strip() else None
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Telemetry package for task.
|
|
3
|
+
|
|
4
|
+
This package provides functionality for capturing telemetry events
|
|
5
|
+
and metrics from the task system.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
# Setup logging
|
|
11
|
+
from agents.task.logging_config import get_task_logger
|
|
12
|
+
logger = get_task_logger('telemetry')
|
|
13
|
+
if not logger.handlers:
|
|
14
|
+
handler = logging.StreamHandler()
|
|
15
|
+
formatter = logging.Formatter('%(asctime)s %(levelname)-8s [%(name)s] %(message)s')
|
|
16
|
+
handler.setFormatter(formatter)
|
|
17
|
+
logger.addHandler(handler)
|
|
18
|
+
logger.setLevel(logging.INFO)
|
|
19
|
+
|
|
20
|
+
# Import primary classes - safe imports that don't create circular dependencies
|
|
21
|
+
from agents.task.telemetry.views import (
|
|
22
|
+
BaseTelemetryEvent,
|
|
23
|
+
AgentRunTelemetryEvent,
|
|
24
|
+
AgentStepTelemetryEvent,
|
|
25
|
+
AgentEndTelemetryEvent,
|
|
26
|
+
LLMRequestTelemetryEvent,
|
|
27
|
+
SessionRelationshipTelemetryEvent,
|
|
28
|
+
InterfaceTelemetryEvent,
|
|
29
|
+
SessionStartTelemetryEvent,
|
|
30
|
+
SessionCompletionTelemetryEvent,
|
|
31
|
+
ProviderFailureEvent,
|
|
32
|
+
ProviderFallbackSuccessEvent
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Lazy import ProductTelemetry to avoid circular references
|
|
36
|
+
# This way consumers can import the module without triggering additional imports
|
|
37
|
+
ProductTelemetry = None
|
|
38
|
+
|
|
39
|
+
def get_telemetry():
|
|
40
|
+
"""Get the ProductTelemetry instance safely using singleton pattern"""
|
|
41
|
+
from agents.task.path import get_safe_singleton
|
|
42
|
+
from agents.task.telemetry.service import ProductTelemetry
|
|
43
|
+
return get_safe_singleton(ProductTelemetry)()
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"get_telemetry", # New function for safe access
|
|
47
|
+
"BaseTelemetryEvent",
|
|
48
|
+
"AgentRunTelemetryEvent",
|
|
49
|
+
"AgentStepTelemetryEvent",
|
|
50
|
+
"AgentEndTelemetryEvent",
|
|
51
|
+
"LLMRequestTelemetryEvent",
|
|
52
|
+
"SessionRelationshipTelemetryEvent",
|
|
53
|
+
"InterfaceTelemetryEvent",
|
|
54
|
+
"SessionStartTelemetryEvent",
|
|
55
|
+
"SessionCompletionTelemetryEvent",
|
|
56
|
+
"ProviderFailureEvent",
|
|
57
|
+
"ProviderFallbackSuccessEvent"
|
|
58
|
+
]
|