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
webview/activity.py
ADDED
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
"""Global activity stream — the `/activity` terminal's server side.
|
|
2
|
+
|
|
3
|
+
One normalized event shape for everything that happens on the instance:
|
|
4
|
+
|
|
5
|
+
{id, ts, source, user_id, session_id, kind, summary, payload}
|
|
6
|
+
|
|
7
|
+
Sources (all cross-process safe — the webview is a separate process from the
|
|
8
|
+
agent in prod):
|
|
9
|
+
|
|
10
|
+
- ``feed`` — per-session ``feed/*.json`` files (rich agent telemetry),
|
|
11
|
+
pushed live by a single recursive ``watchfiles`` watcher.
|
|
12
|
+
- ``telemetry`` — the durable ``telemetry_events.db`` append-only log
|
|
13
|
+
(cron_run / self_wake / wallet_spend / tool_denied / ...).
|
|
14
|
+
- ``goal`` — ``goals.db::goal_events`` (id-cursor tail).
|
|
15
|
+
- ``skill`` — ``skill_usage.db::skill_install_audit`` (id-cursor tail).
|
|
16
|
+
|
|
17
|
+
Design notes: NO ``from __future__ import annotations`` (repo landmine);
|
|
18
|
+
flags via ``os.environ``/``core.env.bool_env`` (never ``BotConfig.get``);
|
|
19
|
+
everything fail-open — a missing DB / unreadable file degrades to silence,
|
|
20
|
+
never a 500 or a crashed watcher.
|
|
21
|
+
"""
|
|
22
|
+
import asyncio
|
|
23
|
+
import itertools
|
|
24
|
+
import json
|
|
25
|
+
import logging
|
|
26
|
+
import os
|
|
27
|
+
import sqlite3
|
|
28
|
+
import time
|
|
29
|
+
from collections import OrderedDict, deque
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
32
|
+
|
|
33
|
+
from fastapi import APIRouter, HTTPException, Request
|
|
34
|
+
from fastapi.responses import HTMLResponse, JSONResponse
|
|
35
|
+
from fastapi.templating import Jinja2Templates
|
|
36
|
+
|
|
37
|
+
from webview import webgate
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
router = APIRouter()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _templates() -> Jinja2Templates:
|
|
45
|
+
"""Same asset base as server.py/pages.py (packaged dir, repo fallback)."""
|
|
46
|
+
try:
|
|
47
|
+
from core.assets import webgate_asset_dir
|
|
48
|
+
templates_dir = webgate_asset_dir() / "templates"
|
|
49
|
+
except Exception: # fail-open to the repo checkout
|
|
50
|
+
templates_dir = Path(__file__).resolve().parent / "templates"
|
|
51
|
+
return Jinja2Templates(directory=str(templates_dir))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
_TEMPLATES = _templates()
|
|
55
|
+
_TEMPLATES.env.globals["console_display_name"] = webgate.console_display_name
|
|
56
|
+
_TEMPLATES.env.globals["branding"] = webgate.branding_config
|
|
57
|
+
# Posture default for the layout's tenant-nav block (P0-3) — same global as
|
|
58
|
+
# server.py's/pages.py's envs.
|
|
59
|
+
_TEMPLATES.env.globals["is_multitenant_posture"] = webgate.is_multitenant
|
|
60
|
+
try:
|
|
61
|
+
from core.version import get_version
|
|
62
|
+
_TEMPLATES.env.globals["get_version"] = get_version
|
|
63
|
+
except Exception:
|
|
64
|
+
_TEMPLATES.env.globals["get_version"] = lambda: ""
|
|
65
|
+
|
|
66
|
+
# Feed kinds that are pure token-stream noise in a global terminal. They stay
|
|
67
|
+
# visible in the per-session view; the global stream drops them.
|
|
68
|
+
_SUPPRESSED_FEED_KINDS = {"streaming_output"}
|
|
69
|
+
|
|
70
|
+
_PAYLOAD_CAP_BYTES = 16000
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _snip(value: Any, n: int = 100) -> str:
|
|
74
|
+
text = str(value or "").strip().replace("\n", " ")
|
|
75
|
+
return text[: n - 1] + "…" if len(text) > n else text
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _cap_payload(obj: Any) -> Any:
|
|
79
|
+
"""Bound payload size so one huge event can't bloat the buffer/wire."""
|
|
80
|
+
try:
|
|
81
|
+
dumped = json.dumps(obj, default=str)
|
|
82
|
+
except Exception:
|
|
83
|
+
return {"_truncated": True, "keys": []}
|
|
84
|
+
if len(dumped) <= _PAYLOAD_CAP_BYTES:
|
|
85
|
+
return obj
|
|
86
|
+
keys = sorted(obj.keys()) if isinstance(obj, dict) else []
|
|
87
|
+
return {"_truncated": True, "keys": keys, "preview": dumped[:2000]}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# Goal-title cache (P1-6): goal events carry only a goal_id; the title lives in
|
|
91
|
+
# goals.db. Bounded LRU, fail-open (a miss/error is just "no title"), and only
|
|
92
|
+
# successful lookups are cached so a transient DB error can't stick.
|
|
93
|
+
_GOAL_TITLE_CACHE: "OrderedDict[str, str]" = OrderedDict()
|
|
94
|
+
_GOAL_TITLE_CACHE_MAX = 256
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _goal_title(goal_id: Any) -> Optional[str]:
|
|
98
|
+
if not goal_id:
|
|
99
|
+
return None
|
|
100
|
+
gid = str(goal_id)
|
|
101
|
+
cached = _GOAL_TITLE_CACHE.get(gid)
|
|
102
|
+
if cached is not None:
|
|
103
|
+
_GOAL_TITLE_CACHE.move_to_end(gid)
|
|
104
|
+
return cached
|
|
105
|
+
db_path = os.path.join(_data_dir(), "goals.db")
|
|
106
|
+
try:
|
|
107
|
+
con = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True, timeout=1.0)
|
|
108
|
+
try:
|
|
109
|
+
row = con.execute("SELECT title FROM goals WHERE id=?", (gid,)).fetchone()
|
|
110
|
+
finally:
|
|
111
|
+
con.close()
|
|
112
|
+
except Exception:
|
|
113
|
+
return None
|
|
114
|
+
title = str(row[0]) if row and row[0] else None
|
|
115
|
+
if title:
|
|
116
|
+
_GOAL_TITLE_CACHE[gid] = title
|
|
117
|
+
while len(_GOAL_TITLE_CACHE) > _GOAL_TITLE_CACHE_MAX:
|
|
118
|
+
_GOAL_TITLE_CACHE.popitem(last=False)
|
|
119
|
+
return title
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _ensure_goal_title(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
123
|
+
"""Fill ``title`` from goals.db when a goal event carries only goal_id."""
|
|
124
|
+
if isinstance(data, dict) and data.get("goal_id") and not data.get("title"):
|
|
125
|
+
title = _goal_title(data.get("goal_id"))
|
|
126
|
+
if title:
|
|
127
|
+
data["title"] = title
|
|
128
|
+
return data
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def summarize(kind: str, data: Dict[str, Any]) -> str:
|
|
132
|
+
"""One terminal line of human summary per event kind. Defensive: every
|
|
133
|
+
field is optional; unknown kinds fall back to the kind itself."""
|
|
134
|
+
d = data if isinstance(data, dict) else {}
|
|
135
|
+
if kind == "tool_execution":
|
|
136
|
+
name = d.get("tool_name") or d.get("action") or d.get("name") or "?"
|
|
137
|
+
ok = d.get("success", True)
|
|
138
|
+
return f"tool {name} {'ok' if ok else 'FAILED'}"
|
|
139
|
+
if kind == "llm_request":
|
|
140
|
+
model = d.get("model_name") or d.get("model") or "?"
|
|
141
|
+
tokens = d.get("token_count") or d.get("total_tokens") or 0
|
|
142
|
+
cost = d.get("cost_estimate")
|
|
143
|
+
tail = f" ${cost:.4f}" if isinstance(cost, (int, float)) and cost else ""
|
|
144
|
+
return f"llm {model} {tokens}tk{tail}"
|
|
145
|
+
if kind == "step":
|
|
146
|
+
n = d.get("iteration", d.get("step", "?"))
|
|
147
|
+
note = _snip(d.get("task_progress") or d.get("reasoning") or "", 80)
|
|
148
|
+
return f"step {n}" + (f": {note}" if note else "")
|
|
149
|
+
if kind == "session_start":
|
|
150
|
+
return f"session started: {_snip(d.get('task') or d.get('task_description') or '', 90)}"
|
|
151
|
+
if kind in ("session_completion", "task_complete"):
|
|
152
|
+
return "session completed"
|
|
153
|
+
if kind == "agent_end":
|
|
154
|
+
return "agent ended"
|
|
155
|
+
if kind == "agent_registration":
|
|
156
|
+
return f"agent {d.get('agent_name') or d.get('name') or '?'} registered"
|
|
157
|
+
if kind == "available_actions":
|
|
158
|
+
actions = d.get("actions")
|
|
159
|
+
count = len(actions) if isinstance(actions, (list, dict)) else d.get("count", "?")
|
|
160
|
+
return f"actions registered ({count})"
|
|
161
|
+
if kind == "status":
|
|
162
|
+
return f"status → {d.get('status', '?')}"
|
|
163
|
+
if kind == "error":
|
|
164
|
+
return f"ERROR: {_snip(d.get('error_message') or d.get('message') or d.get('error') or '', 120)}"
|
|
165
|
+
if kind == "cron_run":
|
|
166
|
+
return f"cron {d.get('job_id', '?')} → {d.get('outcome', '?')}"
|
|
167
|
+
if kind == "self_wake":
|
|
168
|
+
return f"self-wake → {d.get('outcome', '?')} ({_snip(d.get('reason'), 60)})"
|
|
169
|
+
if kind == "wallet_spend":
|
|
170
|
+
return f"wallet spend ${d.get('amount_usd', '?')} @ {d.get('venue', '?')}"
|
|
171
|
+
if kind == "tool_denied":
|
|
172
|
+
return f"tool DENIED: {d.get('tool') or d.get('tool_name') or '?'}"
|
|
173
|
+
if kind == "tool_timeout":
|
|
174
|
+
return f"tool timeout: {d.get('tool') or d.get('tool_name') or '?'}"
|
|
175
|
+
if kind == "autonomy_tick":
|
|
176
|
+
return f"autonomy tick {d.get('loop', '?')} alive={d.get('alive', '?')}"
|
|
177
|
+
if kind == "owner_notice":
|
|
178
|
+
return f"owner notice: {_snip(d.get('message') or d.get('text') or '', 100)}"
|
|
179
|
+
if kind == "skill_install":
|
|
180
|
+
return f"skill installed: {d.get('name', '?')} (by {d.get('approver') or d.get('source') or '?'})"
|
|
181
|
+
if kind.startswith("goal_"):
|
|
182
|
+
# goal_run rides outcome (started/done/failed/blocked); goal-board
|
|
183
|
+
# events carry the verb in the kind (created/claimed/succeeded/...).
|
|
184
|
+
verb = kind[len("goal_"):]
|
|
185
|
+
outcome = d.get("outcome") or d.get("status")
|
|
186
|
+
if outcome:
|
|
187
|
+
verb = f"{verb} {outcome}" if verb == "run" else f"{verb} → {outcome}"
|
|
188
|
+
title = _snip(d.get("title") or "", 70)
|
|
189
|
+
gid = _snip(d.get("goal_id") or "?", 12)
|
|
190
|
+
line = f"goal {verb}: {title} ({gid[:8]})" if title else f"goal {gid[:8]} {verb}"
|
|
191
|
+
reason = d.get("reason") or d.get("error")
|
|
192
|
+
if reason and str(outcome or "") in ("failed", "blocked", "gave_up"):
|
|
193
|
+
line += f" — {_snip(reason, 80)}"
|
|
194
|
+
return line
|
|
195
|
+
return kind
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def normalize_feed_event(user_id: str, session_id: str, raw: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
199
|
+
"""Normalize one per-session feed file's JSON into the activity shape.
|
|
200
|
+
|
|
201
|
+
Handles both writer shapes: telemetry service files ``{..., _seq, _ts_ms,
|
|
202
|
+
_id}`` and SessionManager files ``{timestamp, type, data}``. Returns None
|
|
203
|
+
for suppressed kinds.
|
|
204
|
+
"""
|
|
205
|
+
if not isinstance(raw, dict):
|
|
206
|
+
return None
|
|
207
|
+
kind = raw.get("type") or raw.get("event_type") or "event"
|
|
208
|
+
if kind in _SUPPRESSED_FEED_KINDS:
|
|
209
|
+
return None
|
|
210
|
+
if raw.get("_ts_ms"):
|
|
211
|
+
ts = float(raw["_ts_ms"]) / 1000.0
|
|
212
|
+
else:
|
|
213
|
+
try:
|
|
214
|
+
ts = float(raw.get("timestamp") or 0.0) or time.time()
|
|
215
|
+
except (TypeError, ValueError):
|
|
216
|
+
ts = time.time()
|
|
217
|
+
data = raw.get("data") if isinstance(raw.get("data"), dict) else raw
|
|
218
|
+
ev_id = raw.get("_id") or f"feed:{session_id}:{raw.get('_seq') or int(ts * 1000)}"
|
|
219
|
+
return {
|
|
220
|
+
"id": ev_id,
|
|
221
|
+
"ts": ts,
|
|
222
|
+
"source": "feed",
|
|
223
|
+
"user_id": user_id or "",
|
|
224
|
+
"session_id": session_id or "",
|
|
225
|
+
"kind": kind,
|
|
226
|
+
"summary": summarize(kind, data),
|
|
227
|
+
"payload": _cap_payload(raw),
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _parse_json_field(value: Any) -> Dict[str, Any]:
|
|
232
|
+
if isinstance(value, dict):
|
|
233
|
+
return value
|
|
234
|
+
try:
|
|
235
|
+
parsed = json.loads(value or "{}")
|
|
236
|
+
return parsed if isinstance(parsed, dict) else {}
|
|
237
|
+
except Exception:
|
|
238
|
+
return {}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def normalize_db_event(source: str, row: Dict[str, Any]) -> Dict[str, Any]:
|
|
242
|
+
"""Normalize one SQLite tail row (telemetry / goal / skill) into the
|
|
243
|
+
activity shape."""
|
|
244
|
+
if source == "goal":
|
|
245
|
+
payload = _parse_json_field(row.get("payload"))
|
|
246
|
+
payload.setdefault("goal_id", row.get("goal_id"))
|
|
247
|
+
_ensure_goal_title(payload)
|
|
248
|
+
kind = f"goal_{row.get('kind', 'event')}"
|
|
249
|
+
return {
|
|
250
|
+
"id": f"goal:{row.get('id')}",
|
|
251
|
+
"ts": float(row.get("created_at") or 0.0),
|
|
252
|
+
"source": "goal",
|
|
253
|
+
"user_id": str(payload.get("user_id") or ""),
|
|
254
|
+
"session_id": str(payload.get("session_id") or ""),
|
|
255
|
+
"kind": kind,
|
|
256
|
+
"summary": summarize(kind, payload),
|
|
257
|
+
"payload": _cap_payload(payload),
|
|
258
|
+
}
|
|
259
|
+
if source == "skill":
|
|
260
|
+
payload = {
|
|
261
|
+
"name": row.get("name"),
|
|
262
|
+
"source": row.get("source"),
|
|
263
|
+
"resolved_sha": row.get("resolved_sha"),
|
|
264
|
+
"approver": row.get("approver"),
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
"id": f"skill:{row.get('id')}",
|
|
268
|
+
"ts": float(row.get("ts") or 0.0),
|
|
269
|
+
"source": "skill",
|
|
270
|
+
"user_id": str(row.get("user_id") or ""),
|
|
271
|
+
"session_id": "",
|
|
272
|
+
"kind": "skill_install",
|
|
273
|
+
"summary": summarize("skill_install", payload),
|
|
274
|
+
"payload": payload,
|
|
275
|
+
}
|
|
276
|
+
# default: durable telemetry event log row
|
|
277
|
+
attrs = _parse_json_field(row.get("attrs"))
|
|
278
|
+
kind = str(row.get("kind") or "event")
|
|
279
|
+
if kind.startswith("goal"):
|
|
280
|
+
_ensure_goal_title(attrs)
|
|
281
|
+
return {
|
|
282
|
+
"id": f"telemetry:{row.get('id')}",
|
|
283
|
+
"ts": float(row.get("ts") or 0.0),
|
|
284
|
+
"source": "telemetry",
|
|
285
|
+
"user_id": str(row.get("user_id") or ""),
|
|
286
|
+
"session_id": str(row.get("session_id") or ""),
|
|
287
|
+
"kind": kind,
|
|
288
|
+
"summary": summarize(kind, attrs),
|
|
289
|
+
"payload": _cap_payload(attrs),
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class SqliteTail:
|
|
294
|
+
"""Id-cursor tail over an append-only SQLite table (read-only, WAL-safe).
|
|
295
|
+
|
|
296
|
+
``prime()`` sets the cursor to the current MAX(id) so a fresh webview
|
|
297
|
+
never floods the stream with history; ``poll()`` returns only rows that
|
|
298
|
+
arrived since. A missing DB/table is silence, never an error — the tail
|
|
299
|
+
starts delivering when the file appears.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
def __init__(self, db_path: str, table: str, id_col: str = "id"):
|
|
303
|
+
self.db_path = str(db_path)
|
|
304
|
+
self.table = table
|
|
305
|
+
self.id_col = id_col
|
|
306
|
+
self.cursor = 0
|
|
307
|
+
|
|
308
|
+
def _query(self, sql: str, params: tuple = ()) -> List[Dict[str, Any]]:
|
|
309
|
+
con = sqlite3.connect(f"file:{self.db_path}?mode=ro", uri=True, timeout=1.0)
|
|
310
|
+
try:
|
|
311
|
+
con.row_factory = sqlite3.Row
|
|
312
|
+
return [dict(r) for r in con.execute(sql, params).fetchall()]
|
|
313
|
+
finally:
|
|
314
|
+
con.close()
|
|
315
|
+
|
|
316
|
+
def prime(self) -> None:
|
|
317
|
+
try:
|
|
318
|
+
rows = self._query(f"SELECT MAX({self.id_col}) AS m FROM {self.table}")
|
|
319
|
+
self.cursor = int(rows[0]["m"] or 0)
|
|
320
|
+
except Exception:
|
|
321
|
+
self.cursor = 0
|
|
322
|
+
|
|
323
|
+
def poll(self, limit: int = 500) -> List[Dict[str, Any]]:
|
|
324
|
+
try:
|
|
325
|
+
rows = self._query(
|
|
326
|
+
f"SELECT * FROM {self.table} WHERE {self.id_col} > ? "
|
|
327
|
+
f"ORDER BY {self.id_col} ASC LIMIT ?",
|
|
328
|
+
(self.cursor, limit),
|
|
329
|
+
)
|
|
330
|
+
except Exception:
|
|
331
|
+
return []
|
|
332
|
+
if rows:
|
|
333
|
+
self.cursor = int(rows[-1][self.id_col])
|
|
334
|
+
return rows
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _data_dir() -> str:
|
|
338
|
+
"""Data home for goals.db/skill_usage.db — same resolution as pages.py."""
|
|
339
|
+
return os.environ.get("POLYROB_DATA_DIR", "data")
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _sessions_data_root() -> Optional[str]:
|
|
343
|
+
"""The PathManager session root (feed dirs + telemetry_events.db)."""
|
|
344
|
+
try:
|
|
345
|
+
from agents.task.path import pm
|
|
346
|
+
return str(pm().data_root)
|
|
347
|
+
except Exception:
|
|
348
|
+
return None
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def activity_db_sources() -> List[Tuple[str, SqliteTail]]:
|
|
352
|
+
"""The three SQLite tails feeding the system half of the stream."""
|
|
353
|
+
root = _sessions_data_root()
|
|
354
|
+
tel = (
|
|
355
|
+
os.path.join(root, "telemetry_events.db")
|
|
356
|
+
if root
|
|
357
|
+
else os.path.join(_data_dir(), "telemetry_events.db")
|
|
358
|
+
)
|
|
359
|
+
return [
|
|
360
|
+
("telemetry", SqliteTail(tel, "telemetry_events")),
|
|
361
|
+
("goal", SqliteTail(os.path.join(_data_dir(), "goals.db"), "goal_events")),
|
|
362
|
+
("skill", SqliteTail(os.path.join(_data_dir(), "skill_usage.db"), "skill_install_audit")),
|
|
363
|
+
]
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def feed_path_info(path: str, data_root: str) -> Optional[Tuple[str, str]]:
|
|
367
|
+
"""Derive ``(user_id, session_id)`` from a feed file path, or None.
|
|
368
|
+
|
|
369
|
+
Accepts exactly ``{data_root}/{user}/{session}/feed/*.json``.
|
|
370
|
+
"""
|
|
371
|
+
try:
|
|
372
|
+
rel = Path(path).resolve().relative_to(Path(data_root).resolve())
|
|
373
|
+
except Exception:
|
|
374
|
+
return None
|
|
375
|
+
parts = rel.parts
|
|
376
|
+
if len(parts) != 4 or parts[2] != "feed" or not parts[3].endswith(".json"):
|
|
377
|
+
return None
|
|
378
|
+
return parts[0], parts[1]
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class ActivityHub:
|
|
382
|
+
"""In-process fan-in for normalized activity events.
|
|
383
|
+
|
|
384
|
+
Ring-buffers the last 1000 events for reconnect snapshots/backfill and
|
|
385
|
+
owns the watcher + tail loop lifecycle (started lazily on first
|
|
386
|
+
``join_activity``, stopped when the activity room empties).
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
def __init__(self):
|
|
390
|
+
self.buffer: deque = deque(maxlen=1000)
|
|
391
|
+
self._counter = itertools.count(1)
|
|
392
|
+
self._sio = None
|
|
393
|
+
self._tasks: List[asyncio.Task] = []
|
|
394
|
+
self._sources: List[Tuple[str, SqliteTail]] = []
|
|
395
|
+
self._seen_files: deque = deque(maxlen=2048)
|
|
396
|
+
self._seen_set: set = set()
|
|
397
|
+
self.started = False
|
|
398
|
+
|
|
399
|
+
def record(self, ev: Dict[str, Any]) -> Dict[str, Any]:
|
|
400
|
+
if not ev.get("id"):
|
|
401
|
+
ev["id"] = f"act:{next(self._counter)}"
|
|
402
|
+
self.buffer.append(ev)
|
|
403
|
+
return ev
|
|
404
|
+
|
|
405
|
+
def recent(self, limit: int = 200) -> List[Dict[str, Any]]:
|
|
406
|
+
if limit <= 0:
|
|
407
|
+
return []
|
|
408
|
+
return list(self.buffer)[-limit:]
|
|
409
|
+
|
|
410
|
+
# --- lifecycle --------------------------------------------------------- #
|
|
411
|
+
|
|
412
|
+
def start(self, sio) -> None:
|
|
413
|
+
"""Start the tail loop + feed watcher (idempotent; needs a running loop)."""
|
|
414
|
+
if self.started:
|
|
415
|
+
return
|
|
416
|
+
self.started = True
|
|
417
|
+
self._sio = sio
|
|
418
|
+
self._sources = activity_db_sources()
|
|
419
|
+
for _, tail in self._sources:
|
|
420
|
+
tail.prime()
|
|
421
|
+
self._tasks = [
|
|
422
|
+
asyncio.create_task(self._tail_loop()),
|
|
423
|
+
asyncio.create_task(self._run_watcher()),
|
|
424
|
+
]
|
|
425
|
+
logger.info("activity hub started (tails + feed watcher)")
|
|
426
|
+
|
|
427
|
+
def stop(self) -> None:
|
|
428
|
+
"""Cancel the loops (sync contexts / backstop). Prefer ``aclose()``
|
|
429
|
+
from async code so the cancelled tasks are actually awaited."""
|
|
430
|
+
self.started = False
|
|
431
|
+
for task in self._tasks:
|
|
432
|
+
task.cancel()
|
|
433
|
+
self._tasks = []
|
|
434
|
+
logger.info("activity hub stopped")
|
|
435
|
+
|
|
436
|
+
async def aclose(self) -> None:
|
|
437
|
+
"""Cancel AND drain the loops — no pending-task warnings at loop close."""
|
|
438
|
+
self.started = False
|
|
439
|
+
tasks, self._tasks = self._tasks, []
|
|
440
|
+
for task in tasks:
|
|
441
|
+
task.cancel()
|
|
442
|
+
for task in tasks:
|
|
443
|
+
try:
|
|
444
|
+
await task
|
|
445
|
+
except (asyncio.CancelledError, Exception):
|
|
446
|
+
pass
|
|
447
|
+
logger.info("activity hub stopped")
|
|
448
|
+
|
|
449
|
+
async def _emit(self, ev: Dict[str, Any]) -> None:
|
|
450
|
+
self.record(ev)
|
|
451
|
+
if self._sio is not None:
|
|
452
|
+
try:
|
|
453
|
+
await self._sio.emit("activity_event", ev, room="activity")
|
|
454
|
+
except Exception as exc: # emit failure must never kill the loops
|
|
455
|
+
logger.debug(f"activity emit failed: {exc}")
|
|
456
|
+
|
|
457
|
+
async def _tail_loop(self) -> None:
|
|
458
|
+
try:
|
|
459
|
+
interval = float(os.environ.get("WEBVIEW_ACTIVITY_TAIL_SEC", "2.0") or 2.0)
|
|
460
|
+
except (TypeError, ValueError):
|
|
461
|
+
interval = 2.0
|
|
462
|
+
while True:
|
|
463
|
+
try:
|
|
464
|
+
for source, tail in self._sources:
|
|
465
|
+
for row in tail.poll():
|
|
466
|
+
await self._emit(normalize_db_event(source, row))
|
|
467
|
+
except asyncio.CancelledError:
|
|
468
|
+
raise
|
|
469
|
+
except Exception as exc:
|
|
470
|
+
logger.debug(f"activity tail error: {exc}")
|
|
471
|
+
await asyncio.sleep(interval)
|
|
472
|
+
|
|
473
|
+
async def _run_watcher(self) -> None:
|
|
474
|
+
try:
|
|
475
|
+
from watchfiles import Change, awatch
|
|
476
|
+
except Exception:
|
|
477
|
+
logger.warning("watchfiles unavailable — feed half of activity stream disabled")
|
|
478
|
+
return
|
|
479
|
+
root = _sessions_data_root()
|
|
480
|
+
if not root or not os.path.isdir(root):
|
|
481
|
+
logger.warning("activity watcher: no session data root — feed stream disabled")
|
|
482
|
+
return
|
|
483
|
+
|
|
484
|
+
def _wants(change, path: str) -> bool:
|
|
485
|
+
return path.endswith(".json") and (os.sep + "feed" + os.sep) in path
|
|
486
|
+
|
|
487
|
+
try:
|
|
488
|
+
async for changes in awatch(root, watch_filter=_wants):
|
|
489
|
+
for change, path in changes:
|
|
490
|
+
if change not in (Change.added, Change.modified):
|
|
491
|
+
continue
|
|
492
|
+
if path in self._seen_set:
|
|
493
|
+
continue # feed files are write-once; first event wins
|
|
494
|
+
self._seen_set.add(path)
|
|
495
|
+
self._seen_files.append(path)
|
|
496
|
+
if len(self._seen_files) == self._seen_files.maxlen:
|
|
497
|
+
oldest = self._seen_files.popleft()
|
|
498
|
+
self._seen_set.discard(oldest)
|
|
499
|
+
info = feed_path_info(path, root)
|
|
500
|
+
if not info:
|
|
501
|
+
continue
|
|
502
|
+
ev = self._read_feed_file(path, info[0], info[1])
|
|
503
|
+
if ev:
|
|
504
|
+
await self._emit(ev)
|
|
505
|
+
except asyncio.CancelledError:
|
|
506
|
+
raise
|
|
507
|
+
except Exception as exc:
|
|
508
|
+
logger.warning(f"activity feed watcher stopped: {exc}")
|
|
509
|
+
|
|
510
|
+
def _read_feed_file(self, path: str, user_id: str, session_id: str) -> Optional[Dict[str, Any]]:
|
|
511
|
+
try:
|
|
512
|
+
with open(path, "r", encoding="utf-8") as fh:
|
|
513
|
+
raw = json.load(fh)
|
|
514
|
+
except Exception:
|
|
515
|
+
return None
|
|
516
|
+
return normalize_feed_event(user_id, session_id, raw)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
_hub: Optional[ActivityHub] = None
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def get_hub() -> ActivityHub:
|
|
523
|
+
global _hub
|
|
524
|
+
if _hub is None:
|
|
525
|
+
_hub = ActivityHub()
|
|
526
|
+
return _hub
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
# --- access gate + HTTP surface --------------------------------------------- #
|
|
530
|
+
|
|
531
|
+
def _require_activity_access(request: Request) -> None:
|
|
532
|
+
"""The global stream is inherently cross-tenant, so:
|
|
533
|
+
|
|
534
|
+
- flag off → 404 (feature not there);
|
|
535
|
+
- local → open (loopback operator IS the owner);
|
|
536
|
+
- own_ops → the auth middleware already required the single owner's
|
|
537
|
+
cookie for any non-public path, which /activity is — nothing extra;
|
|
538
|
+
- multitenant → admin tier / is_admin / the instance owner ONLY. A plain
|
|
539
|
+
authenticated tenant is refused: this page shows everyone's activity.
|
|
540
|
+
"""
|
|
541
|
+
if not webgate.activity_enabled():
|
|
542
|
+
raise HTTPException(status_code=404, detail="Activity stream disabled")
|
|
543
|
+
if not webgate.requires_owner_login():
|
|
544
|
+
return
|
|
545
|
+
if webgate.is_multitenant():
|
|
546
|
+
state = getattr(request, "state", None)
|
|
547
|
+
user_id = getattr(state, "user_id", None)
|
|
548
|
+
tier = getattr(state, "tier", None)
|
|
549
|
+
is_admin = bool(getattr(state, "is_admin", False))
|
|
550
|
+
if is_admin or tier == "admin" or (user_id and user_id == webgate.local_owner_id()):
|
|
551
|
+
return
|
|
552
|
+
raise HTTPException(status_code=403, detail="Owner/admin access required")
|
|
553
|
+
return # own_ops: single-owner model (H2b)
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def _recent_feed_events(per_session: int = 20, sessions: int = 3) -> List[Dict[str, Any]]:
|
|
557
|
+
"""Cold-start seed: the last few feed events of the most recent sessions."""
|
|
558
|
+
root = _sessions_data_root()
|
|
559
|
+
if not root or not os.path.isdir(root):
|
|
560
|
+
return []
|
|
561
|
+
candidates = []
|
|
562
|
+
try:
|
|
563
|
+
with os.scandir(root) as users:
|
|
564
|
+
for user_entry in users:
|
|
565
|
+
if not user_entry.is_dir():
|
|
566
|
+
continue
|
|
567
|
+
try:
|
|
568
|
+
with os.scandir(user_entry.path) as session_dirs:
|
|
569
|
+
for sess_entry in session_dirs:
|
|
570
|
+
if not sess_entry.is_dir():
|
|
571
|
+
continue
|
|
572
|
+
feed = os.path.join(sess_entry.path, "feed")
|
|
573
|
+
if os.path.isdir(feed):
|
|
574
|
+
candidates.append(
|
|
575
|
+
(os.path.getmtime(feed), user_entry.name, sess_entry.name, feed)
|
|
576
|
+
)
|
|
577
|
+
except OSError:
|
|
578
|
+
continue
|
|
579
|
+
except OSError:
|
|
580
|
+
return []
|
|
581
|
+
out: List[Dict[str, Any]] = []
|
|
582
|
+
for _, user_id, session_id, feed in sorted(candidates, reverse=True)[:sessions]:
|
|
583
|
+
try:
|
|
584
|
+
names = sorted(n for n in os.listdir(feed) if n.endswith(".json"))[-per_session:]
|
|
585
|
+
except OSError:
|
|
586
|
+
continue
|
|
587
|
+
for name in names:
|
|
588
|
+
try:
|
|
589
|
+
with open(os.path.join(feed, name), "r", encoding="utf-8") as fh:
|
|
590
|
+
raw = json.load(fh)
|
|
591
|
+
except Exception:
|
|
592
|
+
continue
|
|
593
|
+
ev = normalize_feed_event(user_id, session_id, raw)
|
|
594
|
+
if ev:
|
|
595
|
+
out.append(ev)
|
|
596
|
+
return out
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def _cold_backfill(limit: int) -> List[Dict[str, Any]]:
|
|
600
|
+
"""Backfill when the hub buffer is cold: recent DB rows + recent feeds."""
|
|
601
|
+
events: List[Dict[str, Any]] = []
|
|
602
|
+
for source, tail in activity_db_sources():
|
|
603
|
+
try:
|
|
604
|
+
rows = tail._query(
|
|
605
|
+
f"SELECT * FROM {tail.table} ORDER BY {tail.id_col} DESC LIMIT ?",
|
|
606
|
+
(min(limit, 100),),
|
|
607
|
+
)
|
|
608
|
+
except Exception:
|
|
609
|
+
continue
|
|
610
|
+
events.extend(normalize_db_event(source, row) for row in reversed(rows))
|
|
611
|
+
events.extend(_recent_feed_events())
|
|
612
|
+
events.sort(key=lambda ev: ev.get("ts", 0.0))
|
|
613
|
+
return events[-limit:]
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
@router.get("/activity", response_class=HTMLResponse)
|
|
617
|
+
async def activity_page(request: Request) -> Any:
|
|
618
|
+
_require_activity_access(request)
|
|
619
|
+
return _TEMPLATES.TemplateResponse(
|
|
620
|
+
"activity.html",
|
|
621
|
+
{"request": request, "read_only": webgate.read_only()},
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
@router.get("/api/activity/backfill")
|
|
626
|
+
async def activity_backfill(request: Request, limit: int = 200) -> JSONResponse:
|
|
627
|
+
_require_activity_access(request)
|
|
628
|
+
try:
|
|
629
|
+
limit = max(1, min(int(limit), 1000))
|
|
630
|
+
except (TypeError, ValueError):
|
|
631
|
+
limit = 200
|
|
632
|
+
hub = get_hub()
|
|
633
|
+
events = hub.recent(limit)
|
|
634
|
+
if not events:
|
|
635
|
+
events = _cold_backfill(limit)
|
|
636
|
+
return JSONResponse({"events": events})
|