polyrob 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agents/README.md +453 -0
- agents/__init__.py +164 -0
- agents/base_agent.py +743 -0
- agents/personality/character.py +119 -0
- agents/personality/character_manager.py +309 -0
- agents/personality/characters/rob.character.json +56 -0
- agents/personality/characters/trump.character.json +63 -0
- agents/personality/persona_render.py +78 -0
- agents/personality/persona_resolver.py +115 -0
- agents/prompt/__init__.py +58 -0
- agents/prompt/base_prompt.py +92 -0
- agents/prompt/system.py +417 -0
- agents/task/README_PATH_MANAGEMENT.md +141 -0
- agents/task/__init__.py +162 -0
- agents/task/agent/__init__.py +70 -0
- agents/task/agent/agent_state.py +408 -0
- agents/task/agent/async_delegation.py +274 -0
- agents/task/agent/autonomy_state.py +275 -0
- agents/task/agent/conversation.py +75 -0
- agents/task/agent/core/__init__.py +6 -0
- agents/task/agent/core/aux_metering.py +61 -0
- agents/task/agent/core/background_review.py +157 -0
- agents/task/agent/core/construction.py +1179 -0
- agents/task/agent/core/conversational_exit.py +49 -0
- agents/task/agent/core/correspondent_gate.py +227 -0
- agents/task/agent/core/curator.py +246 -0
- agents/task/agent/core/episodic_digest.py +103 -0
- agents/task/agent/core/error_recovery.py +480 -0
- agents/task/agent/core/history_io.py +257 -0
- agents/task/agent/core/llm_provisioning.py +287 -0
- agents/task/agent/core/llm_runner.py +547 -0
- agents/task/agent/core/logging_io.py +276 -0
- agents/task/agent/core/loop_detection.py +262 -0
- agents/task/agent/core/memory_prefetch.py +325 -0
- agents/task/agent/core/memory_writer.py +667 -0
- agents/task/agent/core/model_introspection.py +82 -0
- agents/task/agent/core/model_swap.py +177 -0
- agents/task/agent/core/next_action_internal.py +1331 -0
- agents/task/agent/core/output_validation.py +303 -0
- agents/task/agent/core/project_context.py +272 -0
- agents/task/agent/core/resources.py +93 -0
- agents/task/agent/core/result_processing.py +389 -0
- agents/task/agent/core/run_loop.py +661 -0
- agents/task/agent/core/safety_lifecycle.py +228 -0
- agents/task/agent/core/secret_guard.py +293 -0
- agents/task/agent/core/self_wake.py +280 -0
- agents/task/agent/core/session_metadata.py +104 -0
- agents/task/agent/core/step.py +781 -0
- agents/task/agent/core/step_execution.py +398 -0
- agents/task/agent/core/step_telemetry.py +222 -0
- agents/task/agent/core/test_aux_metering.py +50 -0
- agents/task/agent/core/test_billing_failover.py +68 -0
- agents/task/agent/core/test_fatal_error_classifier.py +38 -0
- agents/task/agent/core/test_judge_validation.py +275 -0
- agents/task/agent/core/test_model_swap.py +190 -0
- agents/task/agent/core/test_reflection_offload.py +54 -0
- agents/task/agent/core/turn_input.py +8 -0
- agents/task/agent/core/untrusted_wrap.py +103 -0
- agents/task/agent/core/user_ingress.py +228 -0
- agents/task/agent/hitl_manager.py +396 -0
- agents/task/agent/log_sanitize.py +36 -0
- agents/task/agent/message_manager/config.py +97 -0
- agents/task/agent/message_manager/service.py +758 -0
- agents/task/agent/message_manager/tests.py +305 -0
- agents/task/agent/message_manager/tool_call_builder.py +539 -0
- agents/task/agent/message_manager/tool_message_repair.py +364 -0
- agents/task/agent/message_manager/views.py +143 -0
- agents/task/agent/messages/__init__.py +8 -0
- agents/task/agent/messages/builders.py +309 -0
- agents/task/agent/messages/compactor.py +611 -0
- agents/task/agent/messages/context_references.py +271 -0
- agents/task/agent/messages/filters.py +333 -0
- agents/task/agent/messages/guidance.py +328 -0
- agents/task/agent/messages/persistence.py +448 -0
- agents/task/agent/messages/retrieval.py +506 -0
- agents/task/agent/messages/sqlite_persistence.py +85 -0
- agents/task/agent/messages/test_compaction_upgrade.py +349 -0
- agents/task/agent/messages/test_persistence_subdir_fix.py +28 -0
- agents/task/agent/messages/test_sqlite_persistence.py +54 -0
- agents/task/agent/messages/token_counter.py +532 -0
- agents/task/agent/orchestrator.py +776 -0
- agents/task/agent/profile_manager.py +105 -0
- agents/task/agent/profile_registry.py +173 -0
- agents/task/agent/prompts.py +1090 -0
- agents/task/agent/scenario_registry.py +226 -0
- agents/task/agent/service.py +509 -0
- agents/task/agent/session.py +984 -0
- agents/task/agent/skill_discovery.py +110 -0
- agents/task/agent/skill_frontmatter.py +94 -0
- agents/task/agent/skill_manager.py +1201 -0
- agents/task/agent/skill_store.py +627 -0
- agents/task/agent/skill_validation.py +56 -0
- agents/task/agent/skill_writer.py +492 -0
- agents/task/agent/sub_agent_manager.py +1137 -0
- agents/task/agent/test_session_disk_recovery.py +34 -0
- agents/task/agent/tests.py +513 -0
- agents/task/agent/tool_call_tracker.py +511 -0
- agents/task/agent/views.py +469 -0
- agents/task/config.py +432 -0
- agents/task/constants.py +1384 -0
- agents/task/flag_defaults.py +63 -0
- agents/task/goals/__init__.py +1 -0
- agents/task/goals/autonomy_marker.py +28 -0
- agents/task/goals/board.py +690 -0
- agents/task/goals/completion_judge.py +247 -0
- agents/task/goals/context.py +62 -0
- agents/task/goals/dispatcher.py +848 -0
- agents/task/goals/escalation.py +102 -0
- agents/task/goals/planner.py +114 -0
- agents/task/logging_config.py +155 -0
- agents/task/path.py +991 -0
- agents/task/robust_parse_config.py +400 -0
- agents/task/runtime/__init__.py +0 -0
- agents/task/runtime/run_as_session.py +130 -0
- agents/task/runtime_safety.py +308 -0
- agents/task/session/__init__.py +5 -0
- agents/task/session/browser_pool.py +130 -0
- agents/task/session/cleanup.py +612 -0
- agents/task/session/execution.py +397 -0
- agents/task/session/feed.py +200 -0
- agents/task/session/hitl_ingress.py +201 -0
- agents/task/session/hooks.py +83 -0
- agents/task/session/multi_agent.py +243 -0
- agents/task/session/workspace.py +91 -0
- agents/task/session_registry.py +91 -0
- agents/task/session_route.py +35 -0
- agents/task/sqlite_session_registry.py +261 -0
- agents/task/surface_config.py +185 -0
- agents/task/telemetry/__init__.py +58 -0
- agents/task/telemetry/event_log.py +180 -0
- agents/task/telemetry/formatters.py +642 -0
- agents/task/telemetry/manager.py +571 -0
- agents/task/telemetry/memory_events.py +66 -0
- agents/task/telemetry/self_events.py +53 -0
- agents/task/telemetry/sequence.py +160 -0
- agents/task/telemetry/service.py +1265 -0
- agents/task/telemetry/views.py +882 -0
- agents/task/templates.py +98 -0
- agents/task/test_aux_chain.py +151 -0
- agents/task/test_aux_router.py +41 -0
- agents/task/tests/test_parse_robustness.py +170 -0
- agents/task/tool_defaults.py +110 -0
- agents/task/utils.py +762 -0
- agents/task/utils_json.py +768 -0
- agents/task/utils_webview.py +86 -0
- agents/task/workspace_context.py +229 -0
- agents/task_agent_lite.py +2398 -0
- api/README.md +683 -0
- api/TASK_API_DOCS.md +151 -0
- api/__init__.py +0 -0
- api/a2a/__init__.py +37 -0
- api/a2a/agent_card.py +337 -0
- api/a2a/client.py +657 -0
- api/a2a/endpoints.py +375 -0
- api/a2a/models.py +268 -0
- api/a2a/streaming.py +419 -0
- api/a2a/task_handler.py +929 -0
- api/admin_endpoints.py +1584 -0
- api/app.py +923 -0
- api/auth_constants.py +28 -0
- api/auth_endpoints.py +402 -0
- api/auth_state.py +36 -0
- api/chat_via_task.py +42 -0
- api/conversation_manager.py +231 -0
- api/dependencies.py +186 -0
- api/eip8004_endpoints.py +452 -0
- api/hyperliquid_models.py +174 -0
- api/hyperliquid_routes.py +450 -0
- api/interfaces.py +207 -0
- api/jwt_middleware.py +83 -0
- api/kb/__init__.py +1 -0
- api/kb/endpoints.py +314 -0
- api/mcp_models.py +279 -0
- api/mcp_routes.py +438 -0
- api/middleware.py +526 -0
- api/models.py +176 -0
- api/openai_compat/__init__.py +0 -0
- api/openai_compat/model_map.py +25 -0
- api/openai_compat/models.py +59 -0
- api/openai_compat/router.py +140 -0
- api/payment_endpoints.py +444 -0
- api/payment_verification.py +138 -0
- api/polymarket_models.py +155 -0
- api/polymarket_routes.py +501 -0
- api/pricing_endpoints.py +89 -0
- api/session_routing.py +54 -0
- api/skill_endpoints.py +453 -0
- api/task_http_api.py +1856 -0
- api/webhooks.py +52 -0
- api/x402_endpoints.py +280 -0
- avatar/README.md +44 -0
- avatar/__init__.py +1 -0
- avatar/config/rob.json +7 -0
- avatar/mindprint.js +395 -0
- avatar/renders/rob.meta.json +20 -0
- avatar/renders/rob.png +0 -0
- avatar/studio.html +373 -0
- avatar/webview/avatar-live.js +44 -0
- cli/README.md +89 -0
- cli/__init__.py +0 -0
- cli/cli_surface.py +67 -0
- cli/commands/__init__.py +0 -0
- cli/commands/_bootstrap.py +49 -0
- cli/commands/_errors.py +53 -0
- cli/commands/chat.py +973 -0
- cli/commands/config.py +100 -0
- cli/commands/dashboard.py +103 -0
- cli/commands/doctor.py +222 -0
- cli/commands/email.py +174 -0
- cli/commands/gateway.py +346 -0
- cli/commands/goals.py +509 -0
- cli/commands/init.py +276 -0
- cli/commands/journey.py +30 -0
- cli/commands/kb.py +280 -0
- cli/commands/model.py +107 -0
- cli/commands/owner.py +431 -0
- cli/commands/pfp.py +232 -0
- cli/commands/run.py +414 -0
- cli/commands/serve.py +41 -0
- cli/commands/session.py +684 -0
- cli/commands/skill_install.py +786 -0
- cli/commands/skills.py +128 -0
- cli/commands/subagents.py +100 -0
- cli/commands/surface.py +81 -0
- cli/commands/telegram.py +185 -0
- cli/commands/todos.py +209 -0
- cli/commands/tools.py +102 -0
- cli/commands/update.py +316 -0
- cli/commands/whatsapp.py +175 -0
- cli/config_store.py +276 -0
- cli/gitignore.py +43 -0
- cli/inventory.py +46 -0
- cli/keys.py +101 -0
- cli/persona.py +17 -0
- cli/polyrob.py +137 -0
- cli/ui/__init__.py +64 -0
- cli/ui/activity.py +141 -0
- cli/ui/app.py +538 -0
- cli/ui/banner.py +233 -0
- cli/ui/blocks.py +534 -0
- cli/ui/bootstrap_notice.py +47 -0
- cli/ui/commands/__init__.py +41 -0
- cli/ui/commands/h_cron.py +64 -0
- cli/ui/commands/h_journey.py +164 -0
- cli/ui/commands/h_kb.py +121 -0
- cli/ui/commands/h_learn.py +100 -0
- cli/ui/commands/h_mcp.py +215 -0
- cli/ui/commands/h_self.py +113 -0
- cli/ui/commands/h_skills.py +314 -0
- cli/ui/commands/handlers.py +1672 -0
- cli/ui/commands/registry.py +426 -0
- cli/ui/dialog.py +425 -0
- cli/ui/event_registry.py +110 -0
- cli/ui/event_specs.py +45 -0
- cli/ui/events.py +433 -0
- cli/ui/identity.py +15 -0
- cli/ui/lifecycle.py +159 -0
- cli/ui/live_hooks.py +43 -0
- cli/ui/model_selector.py +532 -0
- cli/ui/persistent_loop.py +181 -0
- cli/ui/pick.py +113 -0
- cli/ui/plain_renderer.py +351 -0
- cli/ui/renderer.py +384 -0
- cli/ui/rich_renderer.py +533 -0
- cli/ui/secrets.py +115 -0
- cli/ui/state.py +376 -0
- cli/ui/statusbar.py +231 -0
- cli/ui/streaming.py +254 -0
- cli/ui/terminal_render.py +105 -0
- cli/ui/theme.py +132 -0
- cli/update/__init__.py +6 -0
- cli/update/context.py +121 -0
- cli/update/detect.py +176 -0
- cli/update/engine.py +95 -0
- cli/update/migrate_guarded.py +55 -0
- cli/update/process_guard.py +215 -0
- cli/update/runners.py +78 -0
- cli/update/snapshot.py +270 -0
- cli/update/versions.py +205 -0
- core/README.md +377 -0
- core/__init__.py +174 -0
- core/assets.py +87 -0
- core/async_bridge.py +76 -0
- core/autonomy_runtime.py +259 -0
- core/base_component.py +302 -0
- core/bootstrap.py +681 -0
- core/bot.py +310 -0
- core/config.py +833 -0
- core/constants.py +126 -0
- core/container.py +462 -0
- core/db_manifest.py +101 -0
- core/embedding.py +57 -0
- core/env.py +39 -0
- core/exceptions.py +287 -0
- core/flags.py +144 -0
- core/flags_catalog.py +345 -0
- core/home_migration.py +62 -0
- core/identity.py +118 -0
- core/initialization.py +1004 -0
- core/instance.py +537 -0
- core/interactive_gate.py +96 -0
- core/logging.py +436 -0
- core/owner_doc_writer.py +176 -0
- core/pairing.py +193 -0
- core/path_safety.py +20 -0
- core/paths.py +27 -0
- core/payment_config.py +21 -0
- core/permissions.py +322 -0
- core/runtime_config.py +96 -0
- core/runtime_paths.py +135 -0
- core/seams.py +35 -0
- core/secret_scan.py +25 -0
- core/secret_scrub.py +71 -0
- core/secrets.py +35 -0
- core/security_logging_filter.py +148 -0
- core/self_context_writer.py +333 -0
- core/self_evolution.py +294 -0
- core/session_context.py +47 -0
- core/sqlite_util.py +59 -0
- core/surfaces/__init__.py +16 -0
- core/surfaces/access.py +118 -0
- core/surfaces/binding.py +88 -0
- core/surfaces/bootstrap.py +78 -0
- core/surfaces/circuit.py +168 -0
- core/surfaces/continuity.py +77 -0
- core/surfaces/correspondents.py +228 -0
- core/surfaces/dispatcher.py +216 -0
- core/surfaces/envelopes.py +75 -0
- core/surfaces/gc.py +30 -0
- core/surfaces/idempotency.py +57 -0
- core/surfaces/inbound_webhook.py +124 -0
- core/surfaces/media.py +32 -0
- core/surfaces/message_router.py +91 -0
- core/surfaces/outbound_allowlist.py +80 -0
- core/surfaces/outbound_dispatcher.py +110 -0
- core/surfaces/outbound_mirror.py +37 -0
- core/surfaces/outbound_queue.py +107 -0
- core/surfaces/outbound_target.py +12 -0
- core/surfaces/owner_admin.py +29 -0
- core/surfaces/proactive.py +78 -0
- core/surfaces/progress.py +108 -0
- core/surfaces/rate_bucket.py +21 -0
- core/surfaces/registry.py +51 -0
- core/surfaces/rendering.py +53 -0
- core/surfaces/send_policy.py +14 -0
- core/surfaces/serialize.py +24 -0
- core/surfaces/session_chat_registry.py +112 -0
- core/surfaces/session_policy.py +60 -0
- core/surfaces/surface.py +288 -0
- core/surfaces/transcription.py +88 -0
- core/surfaces/voice_echo.py +26 -0
- core/surfaces/voice_guard.py +23 -0
- core/tickers.py +174 -0
- core/tool_catalog.py +192 -0
- core/version.py +75 -0
- core/wallet/__init__.py +5 -0
- core/wallet/agent_wallet.py +59 -0
- core/wallet/audit_sink.py +61 -0
- core/wallet/config.py +70 -0
- core/wallet/factory.py +87 -0
- core/wallet/policy.py +104 -0
- core/wallet/signer.py +51 -0
- cron/README.md +65 -0
- cron/__init__.py +0 -0
- cron/delivery.py +277 -0
- cron/digest.py +124 -0
- cron/jobs.py +207 -0
- cron/runner.py +297 -0
- cron/schedule.py +194 -0
- cron/scheduler.py +201 -0
- cron/service.py +57 -0
- cron/wake_gate.py +223 -0
- data/__init__.py +1 -0
- data/prompts/autov2_prompts.json +87 -0
- data/prompts/skills/browser-automation/SKILL.md +43 -0
- data/prompts/skills/coding-workflow/SKILL.md +36 -0
- data/prompts/skills/crypto-trading-safety/SKILL.md +53 -0
- data/prompts/skills/document-writing/SKILL.md +33 -0
- data/prompts/skills/email-comms/SKILL.md +33 -0
- data/prompts/skills/file-data-ops/SKILL.md +35 -0
- data/prompts/skills/hyperliquid-account-review/SKILL.md +48 -0
- data/prompts/skills/hyperliquid-market-data/SKILL.md +52 -0
- data/prompts/skills/hyperliquid-trading/SKILL.md +59 -0
- data/prompts/skills/lead-research/SKILL.md +86 -0
- data/prompts/skills/market-research-brief/SKILL.md +104 -0
- data/prompts/skills/person-analyzer/SKILL.md +124 -0
- data/prompts/skills/polymarket-market-research/SKILL.md +54 -0
- data/prompts/skills/polymarket-portfolio-review/SKILL.md +50 -0
- data/prompts/skills/polymarket-trading/SKILL.md +55 -0
- data/prompts/skills/presentation-creator/SKILL.md +36 -0
- data/prompts/skills/project-analyzer/SKILL.md +169 -0
- data/prompts/skills/rules.json +268 -0
- data/prompts/skills/secret-handling/SKILL.md +20 -0
- data/prompts/skills/skill-authoring/SKILL.md +31 -0
- data/prompts/skills/skill-security-review/SKILL.md +27 -0
- data/prompts/skills/social-discovery/SKILL.md +57 -0
- data/prompts/skills/task-planning/SKILL.md +33 -0
- data/prompts/skills/web-research/SKILL.md +39 -0
- data/prompts/skills/web-scraping/SKILL.md +73 -0
- data/prompts/skills/x-engagement/SKILL.md +69 -0
- data/prompts/system_prompts.json +4 -0
- data/subscription_channels.json +5 -0
- modules/README.md +503 -0
- modules/__init__.py +124 -0
- modules/auth/__init__.py +13 -0
- modules/auth/api_key_manager.py +157 -0
- modules/auth/identity_mapper.py +380 -0
- modules/auth/siwe_auth.py +273 -0
- modules/auth/tier_manager.py +173 -0
- modules/base_module.py +190 -0
- modules/credits/__init__.py +29 -0
- modules/credits/balance_manager.py +237 -0
- modules/credits/cost_utils.py +129 -0
- modules/credits/pricing.py +139 -0
- modules/credits/unified_ledger.py +148 -0
- modules/credits/usage_meter.py +310 -0
- modules/credits/usage_tracker.py +674 -0
- modules/database/__init__.py +33 -0
- modules/database/audit_log.py +322 -0
- modules/database/auth_tables.py +276 -0
- modules/database/connection.py +644 -0
- modules/database/connection_pool.py +305 -0
- modules/database/conversation_contexts.py +446 -0
- modules/database/database_manager.py +237 -0
- modules/database/hyperliquid.py +375 -0
- modules/database/polymarket.py +574 -0
- modules/database/user_mcp_servers.py +728 -0
- modules/database/user_profiles.py +562 -0
- modules/database/utils.py +107 -0
- modules/database/x402_tables.py +92 -0
- modules/eip8004/README.md +1069 -0
- modules/eip8004/__init__.py +65 -0
- modules/eip8004/contracts.py +498 -0
- modules/eip8004/models.py +271 -0
- modules/eip8004/registration.py +148 -0
- modules/eip8004/reputation.py +367 -0
- modules/eip8004/validation.py +277 -0
- modules/llm/__init__.py +97 -0
- modules/llm/adapters.py +963 -0
- modules/llm/anthropic_client.py +831 -0
- modules/llm/available_models.py +106 -0
- modules/llm/brain_scrubber.py +137 -0
- modules/llm/cache_hints.py +162 -0
- modules/llm/deepseek_client.py +562 -0
- modules/llm/gemini_client.py +1367 -0
- modules/llm/llm_client.py +1139 -0
- modules/llm/llm_client_registry.py +188 -0
- modules/llm/llm_factory.py +237 -0
- modules/llm/llm_manager.py +1036 -0
- modules/llm/messages.py +374 -0
- modules/llm/model_registry.py +1935 -0
- modules/llm/nvidia_client.py +125 -0
- modules/llm/openai_client.py +800 -0
- modules/llm/openrouter_client.py +969 -0
- modules/llm/profiles.py +178 -0
- modules/llm/test_anthropic_cache_capture.py +35 -0
- modules/llm/test_cached_pricing_derivation.py +24 -0
- modules/llm/think_scrubber.py +444 -0
- modules/llm/token_counter.py +268 -0
- modules/memory/__init__.py +35 -0
- modules/memory/backend_factory.py +83 -0
- modules/memory/cache_manager.py +186 -0
- modules/memory/episodic.py +108 -0
- modules/memory/local_vector_memory_provider.py +552 -0
- modules/memory/memory_manager.py +225 -0
- modules/memory/models.py +312 -0
- modules/memory/provider.py +210 -0
- modules/memory/registry.py +205 -0
- modules/memory/sqlite_memory_provider.py +767 -0
- modules/memory/task/__init__.py +52 -0
- modules/memory/task/compaction_manager.py +157 -0
- modules/memory/task/context_retriever.py +809 -0
- modules/memory/task/hierarchical_memory.py +1002 -0
- modules/memory/task/lexical_retriever.py +71 -0
- modules/memory/task/null_context_manager.py +213 -0
- modules/memory/task/phase_manager.py +625 -0
- modules/memory/task/reflection_service.py +157 -0
- modules/memory/task/semantic_retriever.py +218 -0
- modules/memory/task/task_context_manager.py +1165 -0
- modules/memory/task/test_forgetting_engages.py +24 -0
- modules/memory/task/test_reflection_llm.py +39 -0
- modules/memory/task/test_threat_scan.py +53 -0
- modules/memory/task/threat_scan.py +90 -0
- modules/memory/test_sqlite_memory_provider.py +106 -0
- modules/memory/user_profile_manager.py +527 -0
- modules/payments/__init__.py +11 -0
- modules/payments/deposit_monitor.py +478 -0
- modules/payments/price_oracle.py +70 -0
- modules/payments/treasury_sweeper.py +401 -0
- modules/payments/wallet_generator.py +145 -0
- modules/pfp/__init__.py +1 -0
- modules/pfp/config.py +92 -0
- modules/pfp/mesh.py +565 -0
- modules/pfp/push.py +69 -0
- modules/pfp/renderer.py +85 -0
- modules/pfp/store.py +82 -0
- modules/skills/__init__.py +1 -0
- modules/skills/skill_usage.py +248 -0
- modules/transcription/__init__.py +24 -0
- modules/transcription/base.py +20 -0
- modules/transcription/faster_whisper_transcriber.py +55 -0
- modules/x402/README.md +249 -0
- modules/x402/__init__.py +22 -0
- modules/x402/invoicing.py +416 -0
- modules/x402/middleware.py +364 -0
- modules/x402/settlement_watcher.py +145 -0
- modules/x402/x402_integration.py +344 -0
- polyrob-0.5.0.dist-info/METADATA +482 -0
- polyrob-0.5.0.dist-info/RECORD +745 -0
- polyrob-0.5.0.dist-info/WHEEL +5 -0
- polyrob-0.5.0.dist-info/entry_points.txt +2 -0
- polyrob-0.5.0.dist-info/licenses/LICENSE +21 -0
- polyrob-0.5.0.dist-info/top_level.txt +12 -0
- surfaces/README.md +70 -0
- surfaces/__init__.py +0 -0
- surfaces/email/__init__.py +0 -0
- surfaces/email/dedup.py +44 -0
- surfaces/email/harness.py +187 -0
- surfaces/email/inbound.py +159 -0
- surfaces/email/seed.py +60 -0
- surfaces/email/surface.py +105 -0
- surfaces/telegram/__init__.py +0 -0
- surfaces/telegram/dedup.py +83 -0
- surfaces/telegram/harness.py +886 -0
- surfaces/telegram/inbound.py +145 -0
- surfaces/telegram/interactive_tools.py +49 -0
- surfaces/telegram/markdown.py +347 -0
- surfaces/telegram/rate_limit.py +167 -0
- surfaces/telegram/surface.py +187 -0
- surfaces/telegram/voice.py +52 -0
- surfaces/whatsapp/__init__.py +0 -0
- surfaces/whatsapp/client.py +51 -0
- surfaces/whatsapp/harness.py +62 -0
- surfaces/whatsapp/inbound.py +132 -0
- surfaces/whatsapp/surface.py +64 -0
- surfaces/whatsapp/window.py +34 -0
- tools/README.md +578 -0
- tools/__init__.py +424 -0
- tools/alchemy/__init__.py +5 -0
- tools/alchemy/alchemy_tool.py +389 -0
- tools/anysite/__init__.py +17 -0
- tools/anysite/client.py +88 -0
- tools/anysite/tool.py +89 -0
- tools/base_tool.py +468 -0
- tools/browser/__init__.py +22 -0
- tools/browser/actions.py +118 -0
- tools/browser/browser.py +1457 -0
- tools/browser/browser_manager.py +690 -0
- tools/browser/context.py +1748 -0
- tools/browser/playwright_utils.py +137 -0
- tools/browser/views.py +56 -0
- tools/code_exec/SANDBOX_SECURITY.md +144 -0
- tools/code_exec/__init__.py +178 -0
- tools/code_exec/backend.py +73 -0
- tools/code_exec/backends/__init__.py +1 -0
- tools/code_exec/backends/docker.py +779 -0
- tools/code_exec/backends/local_subprocess.py +154 -0
- tools/code_exec/env_policy.py +45 -0
- tools/code_exec/result.py +42 -0
- tools/code_exec/sandbox_guard.py +59 -0
- tools/code_exec/tool.py +227 -0
- tools/coding/__init__.py +50 -0
- tools/coding/edit.py +107 -0
- tools/coding/search.py +113 -0
- tools/coding/tool.py +353 -0
- tools/collabland/__init__.py +5 -0
- tools/collabland/collabland_tool.py +471 -0
- tools/controller/__init__.py +0 -0
- tools/controller/_helpers.py +165 -0
- tools/controller/action_registration.py +1765 -0
- tools/controller/approval.py +244 -0
- tools/controller/approval_interactive.py +90 -0
- tools/controller/delegation.py +195 -0
- tools/controller/execution.py +797 -0
- tools/controller/execution_context.py +141 -0
- tools/controller/hooks.py +135 -0
- tools/controller/introspection.py +344 -0
- tools/controller/mcp_registrar.py +190 -0
- tools/controller/message_send.py +31 -0
- tools/controller/registry/__init__.py +4 -0
- tools/controller/registry/schema_generators.py +395 -0
- tools/controller/registry/schema_sanitizer.py +406 -0
- tools/controller/registry/service.py +1266 -0
- tools/controller/registry/views.py +151 -0
- tools/controller/service.py +409 -0
- tools/controller/tool_management.py +334 -0
- tools/controller/types.py +28 -0
- tools/controller/views.py +450 -0
- tools/cronjob_tools.py +166 -0
- tools/crypto_trade_gate.py +59 -0
- tools/descriptors.py +452 -0
- tools/dom/__init__.py +0 -0
- tools/dom/history_tree_processor/service.py +244 -0
- tools/dom/history_tree_processor/view.py +99 -0
- tools/dom/service.py +471 -0
- tools/dom/views.py +346 -0
- tools/email_tool.py +403 -0
- tools/exceptions.py +169 -0
- tools/filesystem.py +1038 -0
- tools/filesystem_docproc.py +331 -0
- tools/filesystem_pdf.py +755 -0
- tools/git/__init__.py +42 -0
- tools/git/tool.py +258 -0
- tools/github/__init__.py +33 -0
- tools/github/client.py +94 -0
- tools/github/tool.py +237 -0
- tools/goal_tools.py +309 -0
- tools/hyperliquid/__init__.py +56 -0
- tools/hyperliquid/models.py +233 -0
- tools/hyperliquid/service.py +1680 -0
- tools/knowledge_ingest.py +950 -0
- tools/mcp/README.md +535 -0
- tools/mcp/__init__.py +52 -0
- tools/mcp/catalog.py +115 -0
- tools/mcp/config.py +337 -0
- tools/mcp/mcp_tool.py +1415 -0
- tools/mcp/param_coercion.py +289 -0
- tools/mcp/protocol.py +1520 -0
- tools/mcp/rate_limit.py +50 -0
- tools/mcp/security.py +424 -0
- tools/mcp/self_install.py +111 -0
- tools/mcp/server_manager.py +829 -0
- tools/mcp/subscriptions.py +72 -0
- tools/mcp/user_mcp_service.py +899 -0
- tools/mcp/validation_tracker.py +89 -0
- tools/mcp/views.py +346 -0
- tools/oauth/__init__.py +17 -0
- tools/oauth/manager.py +75 -0
- tools/oauth/provider.py +69 -0
- tools/oauth/providers/__init__.py +1 -0
- tools/oauth/providers/generic_oauth2.py +96 -0
- tools/perplexity_tool.py +280 -0
- tools/polymarket/__init__.py +41 -0
- tools/polymarket/clob_adapter.py +76 -0
- tools/polymarket/models.py +182 -0
- tools/polymarket/service.py +2082 -0
- tools/self_env/__init__.py +61 -0
- tools/self_env/tool.py +274 -0
- tools/shell/__init__.py +80 -0
- tools/shell/backend_pool.py +118 -0
- tools/shell/discipline.py +62 -0
- tools/shell/executor.py +157 -0
- tools/shell/loopback_allow.py +87 -0
- tools/shell/process_registry.py +113 -0
- tools/shell/process_tool.py +131 -0
- tools/shell/state.py +129 -0
- tools/shell/tool.py +151 -0
- tools/task_tool.py +637 -0
- tools/twitter_tool.py +2086 -0
- tools/user_directory.py +179 -0
- tools/web_fetch/__init__.py +3 -0
- tools/web_fetch/fetcher.py +144 -0
- tools/web_fetch/render.py +44 -0
- tools/web_fetch/tool.py +60 -0
- tools/x402/__init__.py +60 -0
- tools/x402/client.py +47 -0
- tools/x402/invoice_tool.py +143 -0
- tools/x402/real_client.py +250 -0
- tools/x402/service.py +131 -0
- utils/README.md +498 -0
- utils/__init__.py +46 -0
- utils/auth_utils.py +114 -0
- utils/bounded_collections.py +162 -0
- utils/circuit_breaker.py +389 -0
- utils/gif_utils.py +435 -0
- utils/markdown_utils.py +291 -0
- utils/message_utils.py +147 -0
- utils/metrics.py +36 -0
- utils/path_validator.py +227 -0
- utils/rate_limit_manager.py +399 -0
- utils/result_size.py +296 -0
- utils/time_utils.py +206 -0
- utils/user_utils.py +188 -0
- webview/README.md +111 -0
- webview/RENAME_AND_ALIGN_HANDOFF.md +200 -0
- webview/__init__.py +8 -0
- webview/activity.py +636 -0
- webview/owner_auth.py +108 -0
- webview/pages.py +448 -0
- webview/repair_sessions.py +399 -0
- webview/server.py +4323 -0
- webview/server_launcher.py +112 -0
- webview/static/css/activity.css +89 -0
- webview/static/css/chat.css +1772 -0
- webview/static/css/components.css +1190 -0
- webview/static/css/config-panel.css +651 -0
- webview/static/css/pages/admin.css +766 -0
- webview/static/css/pages/profile.css +231 -0
- webview/static/css/pages/settings.css +297 -0
- webview/static/css/pages/signin.css +316 -0
- webview/static/css/style.css +4250 -0
- webview/static/css/variables.css +147 -0
- webview/static/css/workspace-fullwidth-fix.css +109 -0
- webview/static/img/favicon.ico +0 -0
- webview/static/js/activity.js +394 -0
- webview/static/js/admin/activity.js +186 -0
- webview/static/js/admin/dashboard.js +163 -0
- webview/static/js/admin/user_detail.js +400 -0
- webview/static/js/admin/users.js +174 -0
- webview/static/js/admin/utils.js +214 -0
- webview/static/js/chat.js +3178 -0
- webview/static/js/config-panel.js +522 -0
- webview/static/js/constants.js +20 -0
- webview/static/js/error-handler.js +226 -0
- webview/static/js/ethers.min.js +1 -0
- webview/static/js/event-filter.js +137 -0
- webview/static/js/event-store.js +351 -0
- webview/static/js/file-attachments.js +374 -0
- webview/static/js/file-loader.js +163 -0
- webview/static/js/index.js +103 -0
- webview/static/js/performance-utils.js +196 -0
- webview/static/js/profile.js +396 -0
- webview/static/js/screenshot.js +386 -0
- webview/static/js/session.js +1527 -0
- webview/static/js/settings.js +1288 -0
- webview/static/js/sidebar-data.js +766 -0
- webview/static/js/sidebar-toggle.js +296 -0
- webview/static/js/socket.io.min.js +7 -0
- webview/static/js/stats.js +355 -0
- webview/static/js/ui-utils.js +420 -0
- webview/static/js/workspace.js +966 -0
- webview/stats_service.py +613 -0
- webview/templates/__init__.py +1 -0
- webview/templates/__pycache__/__init__.cpython-311.pyc +0 -0
- webview/templates/activity.html +36 -0
- webview/templates/admin/activity.html +122 -0
- webview/templates/admin/dashboard.html +112 -0
- webview/templates/admin/user_detail.html +228 -0
- webview/templates/admin/users.html +105 -0
- webview/templates/autonomy.html +60 -0
- webview/templates/error.html +133 -0
- webview/templates/finance.html +90 -0
- webview/templates/identity.html +73 -0
- webview/templates/index.html +42 -0
- webview/templates/layout.html +125 -0
- webview/templates/memory.html +59 -0
- webview/templates/owner_login.html +15 -0
- webview/templates/profile.html +121 -0
- webview/templates/session.html +312 -0
- webview/templates/settings.html +534 -0
- webview/templates/sidebar.html +112 -0
- webview/templates/signin.html +213 -0
- webview/templates/status.html +14 -0
- webview/templates/system.html +45 -0
- webview/webgate.py +196 -0
core/sqlite_util.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Small shared SQLite helper: WAL connection + jittered write-retry (roadmap P6).
|
|
2
|
+
|
|
3
|
+
Centralizes the WAL-with-DELETE-fallback + 'database is locked' retry pattern so
|
|
4
|
+
the cross-process session registry (and, later, the cron store) don't each
|
|
5
|
+
re-implement it. Mirrors Reference ``SessionDB``: WAL for concurrent readers + one
|
|
6
|
+
writer, DELETE fallback on NFS/SMB/FUSE, and 20–150ms jitter to avoid SQLite's
|
|
7
|
+
deterministic backoff convoy under contention.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import random
|
|
12
|
+
import sqlite3
|
|
13
|
+
import time
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
_MAX_RETRIES = 15
|
|
17
|
+
_BUSY_TIMEOUT_S = 1.0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def wal_connect(db_path: str, timeout: float = _BUSY_TIMEOUT_S) -> sqlite3.Connection:
|
|
21
|
+
conn = sqlite3.connect(db_path, timeout=timeout)
|
|
22
|
+
conn.row_factory = sqlite3.Row
|
|
23
|
+
try:
|
|
24
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
25
|
+
except sqlite3.OperationalError:
|
|
26
|
+
conn.execute("PRAGMA journal_mode=DELETE")
|
|
27
|
+
return conn
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def execute_retry(db_path: str, sql: str, params: tuple = (), *, fetch: Optional[str] = None):
|
|
31
|
+
"""Execute one statement with jittered retry on write contention.
|
|
32
|
+
|
|
33
|
+
fetch: None -> rowcount; 'one' -> a Row or None; 'all' -> list[Row].
|
|
34
|
+
"""
|
|
35
|
+
last_err: Optional[Exception] = None
|
|
36
|
+
for _ in range(_MAX_RETRIES):
|
|
37
|
+
try:
|
|
38
|
+
conn = wal_connect(db_path)
|
|
39
|
+
try:
|
|
40
|
+
cur = conn.execute(sql, params)
|
|
41
|
+
if fetch == "one":
|
|
42
|
+
row = cur.fetchone()
|
|
43
|
+
conn.commit()
|
|
44
|
+
return row
|
|
45
|
+
if fetch == "all":
|
|
46
|
+
rows = cur.fetchall()
|
|
47
|
+
conn.commit()
|
|
48
|
+
return rows
|
|
49
|
+
conn.commit()
|
|
50
|
+
return cur.rowcount
|
|
51
|
+
finally:
|
|
52
|
+
conn.close()
|
|
53
|
+
except sqlite3.OperationalError as e:
|
|
54
|
+
if "locked" in str(e).lower() or "busy" in str(e).lower():
|
|
55
|
+
last_err = e
|
|
56
|
+
time.sleep(random.uniform(0.02, 0.15))
|
|
57
|
+
continue
|
|
58
|
+
raise
|
|
59
|
+
raise last_err # type: ignore[misc]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Singular Chat Interface — transport-free surface contract (core)."""
|
|
2
|
+
from core.surfaces.envelopes import (
|
|
3
|
+
MessageKind, SessionSource, Identity, InboundMessage,
|
|
4
|
+
OutboundMessage, SurfaceCapabilities, SendResult,
|
|
5
|
+
)
|
|
6
|
+
from core.surfaces.surface import Surface
|
|
7
|
+
from core.surfaces.session_chat_registry import SessionChatRegistry, build_session_key
|
|
8
|
+
from core.surfaces.message_router import MessageRouter
|
|
9
|
+
from core.surfaces.registry import SurfaceRegistry, register_surface, is_surface_enabled
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"MessageKind", "SessionSource", "Identity", "InboundMessage",
|
|
13
|
+
"OutboundMessage", "SurfaceCapabilities", "SendResult", "Surface",
|
|
14
|
+
"SessionChatRegistry", "build_session_key", "MessageRouter",
|
|
15
|
+
"SurfaceRegistry", "register_surface", "is_surface_enabled",
|
|
16
|
+
]
|
core/surfaces/access.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""WS-A access-tier resolver — classify one inbound's sender into a trust tier.
|
|
2
|
+
|
|
3
|
+
Three tiers, resolved ONCE at the routing boundary and carried into the dispatcher:
|
|
4
|
+
|
|
5
|
+
- **OWNER** — the bound owner principal, the local single-user operator, or a paired
|
|
6
|
+
user. May command the agent (steering turn).
|
|
7
|
+
- **CORRESPONDENT** — a third party the agent INITIATED contact with, i.e. an ACTIVE
|
|
8
|
+
binding in the correspondent registry. Their inbound is DATA delivered only to the
|
|
9
|
+
originating session; it can never command the agent.
|
|
10
|
+
- **DENIED** — anonymous, unknown, group/multi-party, or anything that isn't clearly
|
|
11
|
+
one of the above.
|
|
12
|
+
|
|
13
|
+
Invariants:
|
|
14
|
+
- **Tier = authenticated sender**, never thread membership (the registry keys on the
|
|
15
|
+
sender address; ``thread_id`` only disambiguates among that sender's own sessions).
|
|
16
|
+
- **Fail-closed on the CORRESPONDENT→OWNER boundary**: any fault degrades toward
|
|
17
|
+
DENIED and never UPGRADES a sender to OWNER.
|
|
18
|
+
- **Groups/channels are DENIED in v1** — the envelope is single-principal, so there is
|
|
19
|
+
no safe per-author tiering inside a multi-party chat yet.
|
|
20
|
+
|
|
21
|
+
Pure decision function; no surface/transport imports. Reads ``POLYROB_LOCAL`` directly
|
|
22
|
+
(like ``core/pairing``) to stay on the core side of the core→agents boundary.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
import os
|
|
28
|
+
from enum import Enum
|
|
29
|
+
from typing import Any, Mapping, Optional
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
_BOOL_TRUE = {"1", "true", "yes", "on"}
|
|
34
|
+
|
|
35
|
+
# Single-user local mode (`is_owner(local=True)`) grants OWNER to any non-empty uid.
|
|
36
|
+
# That is correct ONLY for surfaces whose principal is the local operator — NEVER for a
|
|
37
|
+
# network surface where the sender id is a forgeable remote address (Fusion CRITICAL:
|
|
38
|
+
# else any email/telegram sender becomes an owner command-turn under POLYROB_LOCAL).
|
|
39
|
+
_LOCAL_OWNER_SURFACES = {"cli", "local", "repl"}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AccessTier(str, Enum):
|
|
43
|
+
OWNER = "owner"
|
|
44
|
+
CORRESPONDENT = "correspondent"
|
|
45
|
+
DENIED = "denied"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _is_owner_or_paired(container: Any, uid: str, env: Mapping[str, str],
|
|
49
|
+
*, allow_local: bool) -> bool:
|
|
50
|
+
"""True if ``uid`` is the owner/local operator or a paired user. Fail-closed.
|
|
51
|
+
|
|
52
|
+
``allow_local`` gates the single-user local-owner bypass: it is only honoured for a
|
|
53
|
+
trusted local surface (never a network surface — see ``_LOCAL_OWNER_SURFACES``).
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
from core.instance import is_owner, resolve_owner_principal
|
|
57
|
+
local = allow_local and (env.get("POLYROB_LOCAL", "") or "").strip().lower() in _BOOL_TRUE
|
|
58
|
+
if is_owner(uid, owner_principal=resolve_owner_principal(env), local=local):
|
|
59
|
+
return True
|
|
60
|
+
except Exception as e: # never let an owner-check fault grant or crash
|
|
61
|
+
logger.debug("access-tier owner check failed (fail-closed): %s", e)
|
|
62
|
+
try:
|
|
63
|
+
from core.pairing import PairingStore
|
|
64
|
+
cfg = getattr(container, "config", None) if container else None
|
|
65
|
+
data_dir = getattr(cfg, "data_dir", "data") or "data"
|
|
66
|
+
store = PairingStore(os.path.join(data_dir, "pairing.db"))
|
|
67
|
+
if store.is_paired(uid):
|
|
68
|
+
return True
|
|
69
|
+
except Exception as e:
|
|
70
|
+
logger.debug("access-tier pairing check failed (fail-closed): %s", e)
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def resolve_access_tier(
|
|
75
|
+
container: Any,
|
|
76
|
+
identity: Any,
|
|
77
|
+
*,
|
|
78
|
+
thread_id: Optional[str] = None,
|
|
79
|
+
env: Optional[Mapping[str, str]] = None,
|
|
80
|
+
) -> AccessTier:
|
|
81
|
+
"""Classify ``identity`` into an :class:`AccessTier`. Never raises."""
|
|
82
|
+
src = os.environ if env is None else env
|
|
83
|
+
try:
|
|
84
|
+
uid = (str(getattr(identity, "user_id", "")) or "").strip()
|
|
85
|
+
if not uid:
|
|
86
|
+
return AccessTier.DENIED
|
|
87
|
+
|
|
88
|
+
source = getattr(identity, "source", None)
|
|
89
|
+
surface = getattr(source, "surface_id", "") or ""
|
|
90
|
+
chat_type = getattr(source, "chat_type", "dm") or "dm"
|
|
91
|
+
|
|
92
|
+
# v1: no per-author tiering inside a multi-party chat -> DENIED.
|
|
93
|
+
if chat_type != "dm":
|
|
94
|
+
return AccessTier.DENIED
|
|
95
|
+
|
|
96
|
+
if _is_owner_or_paired(container, uid, src,
|
|
97
|
+
allow_local=surface in _LOCAL_OWNER_SURFACES):
|
|
98
|
+
return AccessTier.OWNER
|
|
99
|
+
|
|
100
|
+
# Non-owner: routable ONLY as a known correspondent (active binding).
|
|
101
|
+
registry = container.get_service("correspondent_registry") if container else None
|
|
102
|
+
if registry is not None:
|
|
103
|
+
address = getattr(identity, "raw_user_id", None) or uid
|
|
104
|
+
try:
|
|
105
|
+
row = registry.resolve(surface=surface, address=address, thread_id=thread_id)
|
|
106
|
+
except Exception as e: # fail-closed: a registry fault is NOT an upgrade
|
|
107
|
+
logger.debug("access-tier registry resolve failed (fail-closed): %s", e)
|
|
108
|
+
row = None
|
|
109
|
+
if row is not None:
|
|
110
|
+
return AccessTier.CORRESPONDENT
|
|
111
|
+
|
|
112
|
+
return AccessTier.DENIED
|
|
113
|
+
except Exception as e: # absolute fail-closed backstop
|
|
114
|
+
logger.debug("resolve_access_tier fault (fail-closed to DENIED): %s", e)
|
|
115
|
+
return AccessTier.DENIED
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
__all__ = ["AccessTier", "resolve_access_tier"]
|
core/surfaces/binding.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""P1b-2: bind a session's orchestrator to the Singular Chat outbound bus.
|
|
2
|
+
|
|
3
|
+
Called from TaskAgent.create_session immediately after the orchestrator is built
|
|
4
|
+
and BEFORE orchestrator.initialize() — because _register_stream_callback captures
|
|
5
|
+
orchestrator._message_router / ._chat_session_key BY VALUE when it wires the stream
|
|
6
|
+
mirror, so binding after initialize() would capture None (a permanent no-op).
|
|
7
|
+
|
|
8
|
+
Flag-gated (SINGULAR_CHAT_ENABLED, default OFF) and fail-open: when OFF, or the bus
|
|
9
|
+
isn't installed, or no chat_session_key was supplied (legacy callers: cron/goal/
|
|
10
|
+
`polyrob run`/raw API), it touches NOTHING and returns False -> the orchestrator keeps
|
|
11
|
+
its legacy callback-only path, byte-identical to today.
|
|
12
|
+
"""
|
|
13
|
+
import logging
|
|
14
|
+
from typing import Any, Optional
|
|
15
|
+
|
|
16
|
+
from core.surfaces.envelopes import SessionSource
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def bind_chat_surface(
|
|
22
|
+
orchestrator: Any,
|
|
23
|
+
container: Any,
|
|
24
|
+
*,
|
|
25
|
+
session_source: Optional[SessionSource],
|
|
26
|
+
chat_session_key: Optional[str],
|
|
27
|
+
session_id: str,
|
|
28
|
+
user_id: str,
|
|
29
|
+
) -> bool:
|
|
30
|
+
"""Set the orchestrator's router+key and write the chat<->session registry row.
|
|
31
|
+
|
|
32
|
+
Returns True when the orchestrator was bound (router+key set), False when the
|
|
33
|
+
feature is off / unavailable / no key was supplied.
|
|
34
|
+
"""
|
|
35
|
+
from agents.task.surface_config import SurfaceConfig
|
|
36
|
+
|
|
37
|
+
if not SurfaceConfig.singular_chat_enabled():
|
|
38
|
+
return False
|
|
39
|
+
if container is None or not chat_session_key:
|
|
40
|
+
return False
|
|
41
|
+
router = container.get_service("message_router")
|
|
42
|
+
if router is None:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
orchestrator._message_router = router
|
|
46
|
+
orchestrator._chat_session_key = chat_session_key
|
|
47
|
+
|
|
48
|
+
# Write the durable chat<->session row so publish can resolve key -> surface.
|
|
49
|
+
# Needs the SessionSource (surface_id/chat_id); without it we still bind the
|
|
50
|
+
# mirror but publish will fail-open-drop until a row exists.
|
|
51
|
+
if session_source is not None:
|
|
52
|
+
registry = container.get_service("session_chat_registry")
|
|
53
|
+
if registry is not None:
|
|
54
|
+
try:
|
|
55
|
+
registry.bind(
|
|
56
|
+
chat_session_key, session_id, user_id,
|
|
57
|
+
session_source.surface_id, session_source.chat_id,
|
|
58
|
+
)
|
|
59
|
+
except Exception as e: # fail-open: a bind error must not break session create
|
|
60
|
+
logger.debug("bind_chat_surface registry.bind failed: %s", e)
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def surface_ask_capability(orchestrator: Any) -> Optional[bool]:
|
|
65
|
+
"""Can the surface bound to this orchestrator collect a reply (ask)?
|
|
66
|
+
|
|
67
|
+
Returns True/False when a surface is bound and its capability is known, or None
|
|
68
|
+
when nothing is bound / the bus is absent (the legacy, surface-agnostic case).
|
|
69
|
+
Used by the send_message action to avoid the wait_for_response deadlock: pausing
|
|
70
|
+
a session to wait for a reply on a surface that can't deliver one would hang it.
|
|
71
|
+
"""
|
|
72
|
+
key = getattr(orchestrator, "_chat_session_key", None)
|
|
73
|
+
container = getattr(orchestrator, "container", None)
|
|
74
|
+
if not key or container is None:
|
|
75
|
+
return None
|
|
76
|
+
try:
|
|
77
|
+
registry = container.get_service("session_chat_registry")
|
|
78
|
+
row = registry.resolve(key) if registry is not None else None
|
|
79
|
+
if not row:
|
|
80
|
+
return None
|
|
81
|
+
surface_registry = container.get_service("surface_registry")
|
|
82
|
+
surface = surface_registry.get(row.get("surface_id")) if surface_registry else None
|
|
83
|
+
if surface is None:
|
|
84
|
+
return None
|
|
85
|
+
return bool(surface.capabilities.supports_interactive_ask)
|
|
86
|
+
except Exception as e: # fail-open: unknown -> legacy behavior
|
|
87
|
+
logger.debug("surface_ask_capability failed: %s", e)
|
|
88
|
+
return None
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""P1b-0: construct + register the outbound bus on a DI container.
|
|
2
|
+
|
|
3
|
+
This is the one prerequisite the rest of the Singular Chat Interface depends on:
|
|
4
|
+
nothing else in the codebase instantiates MessageRouter / SessionChatRegistry, so
|
|
5
|
+
without this call container.get_service("message_router") is always None and every
|
|
6
|
+
P1a outbound mirror (plus cron/delivery.py's telegram sink) is silently inert.
|
|
7
|
+
|
|
8
|
+
Gated on SINGULAR_CHAT_ENABLED so flag-OFF means the services never exist -> the
|
|
9
|
+
mirrors stay no-ops -> behavior is byte-identical to today. Idempotent: a second
|
|
10
|
+
call reuses the already-installed router so live surface subscriptions are never
|
|
11
|
+
dropped. Fail-open: any construction error logs and returns False rather than
|
|
12
|
+
breaking startup.
|
|
13
|
+
"""
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def install_surface_bus(container, db_path: str = None) -> bool:
|
|
20
|
+
"""Build SessionChatRegistry + MessageRouter and register them on ``container``.
|
|
21
|
+
|
|
22
|
+
Returns True if the bus is present on the container after the call (installed
|
|
23
|
+
now or already present), False if the flag is OFF or construction failed.
|
|
24
|
+
|
|
25
|
+
``db_path`` defaults to ``<container.config.data_dir>/surfaces.db`` so the bus,
|
|
26
|
+
outbox, and circuit store follow POLYROB_DATA_DIR isolation instead of a hardcoded
|
|
27
|
+
``./data`` inside the code tree. Pass an explicit path to override.
|
|
28
|
+
"""
|
|
29
|
+
from agents.task.surface_config import SurfaceConfig
|
|
30
|
+
|
|
31
|
+
if not SurfaceConfig.singular_chat_enabled():
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
# Idempotent: never clobber a live router (it holds surface subscriptions).
|
|
35
|
+
existing = container.get_service("message_router")
|
|
36
|
+
if existing is not None:
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
if db_path is None:
|
|
40
|
+
import os as _os
|
|
41
|
+
data_dir = getattr(getattr(container, "config", None), "data_dir", "data") or "data"
|
|
42
|
+
db_path = _os.path.join(data_dir, "surfaces.db")
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
from core.surfaces.session_chat_registry import SessionChatRegistry
|
|
46
|
+
from core.surfaces.message_router import MessageRouter
|
|
47
|
+
|
|
48
|
+
registry = SessionChatRegistry(db_path)
|
|
49
|
+
router = MessageRouter(registry)
|
|
50
|
+
container.register_service("session_chat_registry", registry)
|
|
51
|
+
container.register_service("message_router", router)
|
|
52
|
+
|
|
53
|
+
from core.surfaces.outbound_allowlist import OutboundAllowlist
|
|
54
|
+
container.register_service("outbound_allowlist", OutboundAllowlist(db_path))
|
|
55
|
+
|
|
56
|
+
if SurfaceConfig.outbound_queue_enabled():
|
|
57
|
+
import os
|
|
58
|
+
from core.surfaces.outbound_queue import OutboundDeliveryQueue
|
|
59
|
+
from core.surfaces.outbound_dispatcher import OutboundDispatcher
|
|
60
|
+
from core.surfaces.circuit import CircuitStore, SurfaceCircuitBreaker
|
|
61
|
+
q = OutboundDeliveryQueue(os.path.join(os.path.dirname(db_path) or ".", "outbox.db"))
|
|
62
|
+
q.reclaim_inflight(older_than=__import__("time").time() - 120) # restart-recovery
|
|
63
|
+
router.attach_queue(q)
|
|
64
|
+
circuit_store = CircuitStore(
|
|
65
|
+
os.path.join(os.path.dirname(db_path) or ".", "surface_state.db")
|
|
66
|
+
)
|
|
67
|
+
circuit = SurfaceCircuitBreaker(store=circuit_store)
|
|
68
|
+
dispatcher = OutboundDispatcher(q, lambda sid: router._surfaces.get(sid),
|
|
69
|
+
circuit=circuit)
|
|
70
|
+
container.register_service("outbound_queue", q)
|
|
71
|
+
container.register_service("outbound_dispatcher", dispatcher)
|
|
72
|
+
container.register_service("surface_circuit_breaker", circuit)
|
|
73
|
+
logger.info("surface bus: durable outbound queue + dispatcher + circuit breaker constructed")
|
|
74
|
+
logger.info("surface bus installed (session_chat_registry + message_router)")
|
|
75
|
+
return True
|
|
76
|
+
except Exception as e: # fail-open: a bus build error must not break startup
|
|
77
|
+
logger.error("install_surface_bus failed: %s", e, exc_info=True)
|
|
78
|
+
return False
|
core/surfaces/circuit.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Per-surface circuit breaker for the outbound dispatcher.
|
|
2
|
+
|
|
3
|
+
Opens automatically after K consecutive failures (stops hammering a dead platform);
|
|
4
|
+
also supports manual pause/resume so an operator can hold a surface while it's
|
|
5
|
+
in maintenance. An optional CircuitStore persists the paused flag across processes
|
|
6
|
+
so the CLI's `polyrob surface pause` is visible to the worker.
|
|
7
|
+
|
|
8
|
+
No `from __future__ import annotations` — callers may introspect param annotations.
|
|
9
|
+
"""
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CircuitStore:
|
|
17
|
+
"""Tiny SQLite-backed paused-flag store.
|
|
18
|
+
|
|
19
|
+
One table: surface_state(surface_id TEXT PK, paused INTEGER).
|
|
20
|
+
Reads/writes go through core.sqlite_util for WAL + jittered retry.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
_CREATE = (
|
|
24
|
+
"CREATE TABLE IF NOT EXISTS surface_state "
|
|
25
|
+
"(surface_id TEXT PRIMARY KEY, paused INTEGER NOT NULL DEFAULT 0)"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def __init__(self, db_path: str) -> None:
|
|
29
|
+
self._db = db_path
|
|
30
|
+
from core.sqlite_util import wal_connect
|
|
31
|
+
conn = wal_connect(db_path)
|
|
32
|
+
try:
|
|
33
|
+
conn.execute(self._CREATE)
|
|
34
|
+
conn.commit()
|
|
35
|
+
finally:
|
|
36
|
+
conn.close()
|
|
37
|
+
|
|
38
|
+
def pause(self, surface_id: str) -> None:
|
|
39
|
+
from core.sqlite_util import execute_retry
|
|
40
|
+
execute_retry(
|
|
41
|
+
self._db,
|
|
42
|
+
"INSERT INTO surface_state(surface_id, paused) VALUES(?,1) "
|
|
43
|
+
"ON CONFLICT(surface_id) DO UPDATE SET paused=1",
|
|
44
|
+
(surface_id,),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def resume(self, surface_id: str) -> None:
|
|
48
|
+
from core.sqlite_util import execute_retry
|
|
49
|
+
execute_retry(
|
|
50
|
+
self._db,
|
|
51
|
+
"INSERT INTO surface_state(surface_id, paused) VALUES(?,0) "
|
|
52
|
+
"ON CONFLICT(surface_id) DO UPDATE SET paused=0",
|
|
53
|
+
(surface_id,),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def is_paused(self, surface_id: str) -> bool:
|
|
57
|
+
from core.sqlite_util import execute_retry
|
|
58
|
+
row = execute_retry(
|
|
59
|
+
self._db,
|
|
60
|
+
"SELECT paused FROM surface_state WHERE surface_id=?",
|
|
61
|
+
(surface_id,),
|
|
62
|
+
fetch="one",
|
|
63
|
+
)
|
|
64
|
+
return bool(row and row["paused"])
|
|
65
|
+
|
|
66
|
+
def list_all(self) -> list:
|
|
67
|
+
from core.sqlite_util import execute_retry
|
|
68
|
+
rows = execute_retry(
|
|
69
|
+
self._db,
|
|
70
|
+
"SELECT surface_id, paused FROM surface_state ORDER BY surface_id",
|
|
71
|
+
fetch="all",
|
|
72
|
+
)
|
|
73
|
+
return [{"surface_id": r["surface_id"], "paused": bool(r["paused"])} for r in (rows or [])]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class SurfaceCircuitBreaker:
|
|
77
|
+
"""Auto-pauses a surface after `threshold` consecutive failures.
|
|
78
|
+
|
|
79
|
+
In-memory state:
|
|
80
|
+
- ``_counts[surface_id]``: consecutive failure counter (resets on ok).
|
|
81
|
+
- ``_paused``: set of manually paused surface IDs.
|
|
82
|
+
|
|
83
|
+
Optional ``store`` (a ``CircuitStore``): pause/resume also writes the flag to
|
|
84
|
+
SQLite, and ``is_open`` reads it so a worker process sees CLI changes.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(self, threshold: int = 5, store: Optional[CircuitStore] = None) -> None:
|
|
88
|
+
self._threshold = threshold
|
|
89
|
+
self._counts: dict = {} # surface_id -> consecutive fail count
|
|
90
|
+
self._paused: set = set()
|
|
91
|
+
self._store = store
|
|
92
|
+
|
|
93
|
+
# ------------------------------------------------------------------
|
|
94
|
+
# Auto-tripping path
|
|
95
|
+
# ------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
def record_ok(self, surface_id: str) -> None:
|
|
98
|
+
"""Reset the consecutive-fail counter; closes the auto-open state."""
|
|
99
|
+
self._counts[surface_id] = 0
|
|
100
|
+
|
|
101
|
+
def record_fail(self, surface_id: str) -> None:
|
|
102
|
+
"""Increment the consecutive-fail counter; opens when >= threshold."""
|
|
103
|
+
count = self._counts.get(surface_id, 0) + 1
|
|
104
|
+
self._counts[surface_id] = count
|
|
105
|
+
if count >= self._threshold:
|
|
106
|
+
logger.warning(
|
|
107
|
+
"circuit_breaker: surface=%s opened after %d consecutive failures",
|
|
108
|
+
surface_id, count,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# ------------------------------------------------------------------
|
|
112
|
+
# Manual pause/resume path
|
|
113
|
+
# ------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
def pause(self, surface_id: str) -> None:
|
|
116
|
+
"""Manually pause a surface (operator-driven; persisted if store attached)."""
|
|
117
|
+
self._paused.add(surface_id)
|
|
118
|
+
if self._store is not None:
|
|
119
|
+
self._store.pause(surface_id)
|
|
120
|
+
|
|
121
|
+
def resume(self, surface_id: str) -> None:
|
|
122
|
+
"""Resume a paused surface and reset its fail counter."""
|
|
123
|
+
self._paused.discard(surface_id)
|
|
124
|
+
self._counts[surface_id] = 0
|
|
125
|
+
if self._store is not None:
|
|
126
|
+
self._store.resume(surface_id)
|
|
127
|
+
|
|
128
|
+
# ------------------------------------------------------------------
|
|
129
|
+
# State query
|
|
130
|
+
# ------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
def _store_paused(self, surface_id: str) -> bool:
|
|
133
|
+
"""Read the persisted pause flag, fail-open. A store/DB fault (missing or corrupt
|
|
134
|
+
surface_state.db) must NEVER crash a send — treat it as not-paused."""
|
|
135
|
+
if self._store is None:
|
|
136
|
+
return False
|
|
137
|
+
try:
|
|
138
|
+
return bool(self._store.is_paused(surface_id))
|
|
139
|
+
except Exception as e:
|
|
140
|
+
logger.warning("circuit_store: read failed for %s (treating as not paused): %s",
|
|
141
|
+
surface_id, e)
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
def is_open(self, surface_id: str) -> bool:
|
|
145
|
+
"""Return True if the surface should be skipped (open = bad = skip)."""
|
|
146
|
+
if surface_id in self._paused:
|
|
147
|
+
return True
|
|
148
|
+
if (self._counts.get(surface_id, 0)) >= self._threshold:
|
|
149
|
+
return True
|
|
150
|
+
if self._store_paused(surface_id):
|
|
151
|
+
return True
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
def state(self, surface_id: str) -> dict:
|
|
155
|
+
"""Return a snapshot of the surface's circuit state."""
|
|
156
|
+
count = self._counts.get(surface_id, 0)
|
|
157
|
+
manually_paused = surface_id in self._paused
|
|
158
|
+
store_paused = self._store_paused(surface_id)
|
|
159
|
+
auto_open = count >= self._threshold
|
|
160
|
+
return {
|
|
161
|
+
"surface_id": surface_id,
|
|
162
|
+
"consecutive_failures": count,
|
|
163
|
+
"threshold": self._threshold,
|
|
164
|
+
"auto_open": auto_open,
|
|
165
|
+
"manually_paused": manually_paused,
|
|
166
|
+
"store_paused": store_paused,
|
|
167
|
+
"is_open": manually_paused or auto_open or store_paused,
|
|
168
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Idle-reset continuity bridge (Task 6, 2026-07-03).
|
|
2
|
+
|
|
3
|
+
Seeds a short "last time we were discussing X" foundation message into a FRESH chat
|
|
4
|
+
session, sourced from the prior chat episode's summary for the same ``thread_key`` —
|
|
5
|
+
so continuity survives an idle/daily reset. The bridge is literally "recall my last
|
|
6
|
+
episode for this chat key": a consumer of the episodic store (Tasks 1-2), not a
|
|
7
|
+
second summary system.
|
|
8
|
+
|
|
9
|
+
Deviation from the original brief (documented, not an oversight)
|
|
10
|
+
------------------------------------------------------------------
|
|
11
|
+
The brief additionally sketched a ``write_closing_episode`` helper called from
|
|
12
|
+
``core/surfaces/dispatcher.py``'s ``if fresh:`` branch, writing a closing episode for
|
|
13
|
+
the OUTGOING session at the reset boundary (with no summary available there — routing
|
|
14
|
+
code isn't a summarizer). That write is deliberately NOT implemented:
|
|
15
|
+
|
|
16
|
+
``SqliteMemoryProvider.record_episode`` upserts
|
|
17
|
+
``ON CONFLICT(user_id, session_id) DO UPDATE SET summary=excluded.summary`` —
|
|
18
|
+
keyed by ``(user_id, session_id)``, NOT ``thread_key``. Task 6 Part A (see
|
|
19
|
+
``agents/task/session/cleanup.py``) already writes a REAL H-MEM-derived summary for
|
|
20
|
+
that same ``session_id`` when the session is actually torn down. In the common case
|
|
21
|
+
the old session has already been LRU/idle-evicted (cleanup already ran, already wrote
|
|
22
|
+
a good summary) by the time a new inbound message triggers ``fresh`` for its
|
|
23
|
+
thread_key — so a dispatcher-side null-summary write would land AFTER and CLOBBER the
|
|
24
|
+
good one via that same upsert. Rather than add cross-site write-ordering/locking to
|
|
25
|
+
make a second write site safe, this bridge is written from exactly ONE place
|
|
26
|
+
(cleanup()), which is also the only place a real summary is available.
|
|
27
|
+
|
|
28
|
+
Known limitation (v1, acceptable): a session that is still resident — not yet
|
|
29
|
+
LRU/idle-evicted, so its cleanup() (and therefore its summary-bearing episode) hasn't
|
|
30
|
+
run yet — will not bridge on the very next reset boundary for its thread_key; the
|
|
31
|
+
bridge only starts working once that session's cleanup episode has landed. Task 5's
|
|
32
|
+
broader "recent activity" digest still covers that window ("what did you do"), just
|
|
33
|
+
not the more specific "what were we just discussing".
|
|
34
|
+
"""
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import logging
|
|
38
|
+
from typing import Optional
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def build_bridge_message(*, user_id: Optional[str], thread_key: Optional[str]):
|
|
44
|
+
"""Recall the most recent ``kind="chat"`` episode with a non-empty summary for
|
|
45
|
+
``thread_key`` and wrap it as a ``SESSION_BRIDGE`` control message.
|
|
46
|
+
|
|
47
|
+
Returns None when: ``AutonomyConfig.continuity_bridge_enabled()`` is False,
|
|
48
|
+
``thread_key`` is falsy, no prior chat episode exists for it, or every recalled
|
|
49
|
+
episode has an empty/whitespace-only summary. Fail-open — any error returns None.
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
from agents.task.constants import AutonomyConfig
|
|
53
|
+
if not AutonomyConfig.continuity_bridge_enabled() or not thread_key:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
from modules.memory.registry import memory_recall_episodes
|
|
57
|
+
rows = await memory_recall_episodes(
|
|
58
|
+
user_id=user_id, thread_key=thread_key, kind="chat", limit=3, order="newest")
|
|
59
|
+
|
|
60
|
+
summary = None
|
|
61
|
+
for row in rows or []:
|
|
62
|
+
candidate = (getattr(row, "summary", None) or "").strip()
|
|
63
|
+
if candidate:
|
|
64
|
+
summary = candidate
|
|
65
|
+
break
|
|
66
|
+
if not summary:
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
from modules.llm.messages import make_control_message, MessageOrigin
|
|
70
|
+
return make_control_message(
|
|
71
|
+
"Continuing an earlier conversation with this contact. "
|
|
72
|
+
f"Last time: {summary[:600]}",
|
|
73
|
+
MessageOrigin.SESSION_BRIDGE,
|
|
74
|
+
)
|
|
75
|
+
except Exception:
|
|
76
|
+
logger.warning("build_bridge_message failed", exc_info=True)
|
|
77
|
+
return None
|