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
cli/ui/state.py
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""SessionState accumulator for the POLYROB CLI renderer (Phase 1).
|
|
2
|
+
|
|
3
|
+
``SessionState`` is the single source of truth the renderer reads from.
|
|
4
|
+
It is updated in two ways:
|
|
5
|
+
|
|
6
|
+
1. **Push** — ``update(event)`` is called for every incoming ``RenderEvent``.
|
|
7
|
+
2. **Pull** — ``poll(agent)`` is called once per turn, reading live metrics
|
|
8
|
+
from the ``message_manager`` that are not available via the feed.
|
|
9
|
+
|
|
10
|
+
Design notes:
|
|
11
|
+
- No I/O; pure in-memory accumulation.
|
|
12
|
+
- Elapsed time uses ``time.monotonic()`` captured at construction (session
|
|
13
|
+
start) so it doesn't drift with wall-clock skew.
|
|
14
|
+
- Cost is a rolling sum of ``LLMCall.cost_estimate`` values. It is an
|
|
15
|
+
*estimate* — the authoritative total is in ``LLMUsageTracker``, available
|
|
16
|
+
on demand via ``/usage`` (Phase 4).
|
|
17
|
+
- All numeric fields default to zero / None so the renderer can always
|
|
18
|
+
format them safely.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import time
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any, Optional
|
|
27
|
+
|
|
28
|
+
from cli.ui.events import (
|
|
29
|
+
AgentEnd,
|
|
30
|
+
AgentRegistration,
|
|
31
|
+
ErrorEvent,
|
|
32
|
+
IterationDone,
|
|
33
|
+
LLMCall,
|
|
34
|
+
RenderEvent,
|
|
35
|
+
SessionDone,
|
|
36
|
+
SessionStart,
|
|
37
|
+
Step,
|
|
38
|
+
ToolExec,
|
|
39
|
+
)
|
|
40
|
+
from cli.ui.lifecycle import TurnLifecycle
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SessionState:
|
|
44
|
+
"""Mutable accumulator of session metrics for the CLI renderer.
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
model: LLM model name (set from SessionStart).
|
|
48
|
+
provider: LLM provider name (set from first LLMCall).
|
|
49
|
+
started_at: ``time.monotonic()`` at construction.
|
|
50
|
+
status: Human-readable status string.
|
|
51
|
+
|
|
52
|
+
tokens_in: Accumulated prompt tokens.
|
|
53
|
+
tokens_out: Accumulated completion tokens.
|
|
54
|
+
tokens_cached: Placeholder (not emitted by current feed; kept for
|
|
55
|
+
future compatibility with cache-token reporting).
|
|
56
|
+
tokens_total: Accumulated ``token_count`` (may differ from
|
|
57
|
+
in+out due to cache accounting).
|
|
58
|
+
cost_estimate_total: Running sum of ``LLMCall.cost_estimate`` values.
|
|
59
|
+
|
|
60
|
+
step: Current step number.
|
|
61
|
+
max_steps: Maximum steps (not in feed; settable externally).
|
|
62
|
+
tool_calls: Count of tool-execution events seen.
|
|
63
|
+
errors: Count of error events seen.
|
|
64
|
+
|
|
65
|
+
ctx_percent: Context window usage 0–100 (polled).
|
|
66
|
+
ctx_tokens: Raw token count in context (polled).
|
|
67
|
+
ctx_max: Max input tokens (polled).
|
|
68
|
+
compactions: Number of compaction events so far (polled).
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, *, clock: Optional[Any] = None) -> None:
|
|
72
|
+
self._clock = clock or time.monotonic
|
|
73
|
+
self.model: str = ""
|
|
74
|
+
self.provider: str = ""
|
|
75
|
+
# ``started_at`` is the SESSION AGE anchor (used by /session). The live bar
|
|
76
|
+
# shows the per-turn WORK clock via ``lifecycle.active_elapsed()`` instead —
|
|
77
|
+
# session age must never be presented as "elapsed" work time.
|
|
78
|
+
self.started_at: float = self._clock()
|
|
79
|
+
self.status: str = "starting"
|
|
80
|
+
|
|
81
|
+
# The single source of truth for "is a turn active, and for how long".
|
|
82
|
+
# The bar's clock + status word derive from this; feed events do NOT
|
|
83
|
+
# mutate it (only the turn runner does, via begin_turn/end_turn).
|
|
84
|
+
self.lifecycle: TurnLifecycle = TurnLifecycle(clock=self._clock)
|
|
85
|
+
|
|
86
|
+
# Token accounting (accumulated from LLMCall events)
|
|
87
|
+
self.tokens_in: int = 0
|
|
88
|
+
self.tokens_out: int = 0
|
|
89
|
+
self.tokens_cached: int = 0
|
|
90
|
+
self.tokens_total: int = 0
|
|
91
|
+
self.cost_estimate_total: float = 0.0
|
|
92
|
+
|
|
93
|
+
# Step / progress
|
|
94
|
+
self.step: int = 0
|
|
95
|
+
self.max_steps: Optional[int] = None
|
|
96
|
+
self.tool_calls: int = 0
|
|
97
|
+
self.errors: int = 0
|
|
98
|
+
|
|
99
|
+
# Context window metrics (polled from message_manager each turn)
|
|
100
|
+
self.ctx_percent: float = 0.0
|
|
101
|
+
self.ctx_tokens: int = 0
|
|
102
|
+
self.ctx_max: int = 0
|
|
103
|
+
self.compactions: int = 0
|
|
104
|
+
|
|
105
|
+
# Sub-agent grouping: the first registered agent is the "main" agent;
|
|
106
|
+
# any other agent_id/agent_name seen is a sub-agent (delegate_task spawn).
|
|
107
|
+
# Both id and name are stored because step events carry only agent_name
|
|
108
|
+
# (the real AgentStepFormatter never emits agent_id in the step dict).
|
|
109
|
+
self.main_agent_id: str = ""
|
|
110
|
+
self.main_agent_name: str = ""
|
|
111
|
+
# True when the most-recent Step belonged to a sub-agent. tool_execution
|
|
112
|
+
# events carry no agent identity, so the renderer correlates a tool
|
|
113
|
+
# result to the step that declared it to suppress sub-agent tool churn
|
|
114
|
+
# from the default (non-verbose) transcript.
|
|
115
|
+
self.last_step_sub_agent: bool = False
|
|
116
|
+
|
|
117
|
+
# Live-info (D3): count of in-flight delegate_task sub-agents (fed by the
|
|
118
|
+
# orchestrator sub-agent lifecycle hooks the REPL registers) and the name
|
|
119
|
+
# of the most-recent tool action (the in-flight tool for the status line).
|
|
120
|
+
self.subagents_active: int = 0
|
|
121
|
+
self.last_tool: str = ""
|
|
122
|
+
# Slow-polled, cached autonomy snapshot for the 2nd status line (D4).
|
|
123
|
+
# {"goals": int, "cron": int, "review": bool}. None/empty → line hidden.
|
|
124
|
+
# NEVER read the goal/cron SQLite stores on the hot repaint path.
|
|
125
|
+
self.autonomy_snapshot: Optional[dict] = None
|
|
126
|
+
|
|
127
|
+
# poll_usage() bookkeeping: filenames already aggregated so each poll
|
|
128
|
+
# only reads NEW llm_usage files (cheap, incremental).
|
|
129
|
+
self._seen_usage_files: set[str] = set()
|
|
130
|
+
|
|
131
|
+
# ------------------------------------------------------------------
|
|
132
|
+
# Live sub-agent counter (fed by the REPL's orchestrator hooks)
|
|
133
|
+
# ------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
def note_subagent_start(self) -> None:
|
|
136
|
+
"""A delegate_task sub-agent began (orchestrator start hook)."""
|
|
137
|
+
self.subagents_active += 1
|
|
138
|
+
|
|
139
|
+
def note_subagent_end(self) -> None:
|
|
140
|
+
"""A delegate_task sub-agent finished (orchestrator end hook). Floored at 0."""
|
|
141
|
+
self.subagents_active = max(0, self.subagents_active - 1)
|
|
142
|
+
|
|
143
|
+
# ------------------------------------------------------------------
|
|
144
|
+
# Push: event accumulation
|
|
145
|
+
# ------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
def update(self, event: RenderEvent) -> None:
|
|
148
|
+
"""Accumulate state from one ``RenderEvent``."""
|
|
149
|
+
# Extension seam (D2): a registered event mutates state via its spec's
|
|
150
|
+
# ``apply`` — no isinstance branch needed here for new core events.
|
|
151
|
+
from cli.ui.event_registry import RegisteredEvent, get_spec
|
|
152
|
+
if isinstance(event, RegisteredEvent):
|
|
153
|
+
spec = get_spec(event.type)
|
|
154
|
+
if spec is not None:
|
|
155
|
+
spec.apply(self, event)
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
# NOTE: feed events NEVER drive ``status`` — the TurnLifecycle (begin_turn/
|
|
159
|
+
# end_turn at the turn seam) is the single source of truth for the status
|
|
160
|
+
# word + work clock. A background autonomy turn emits the same feed events
|
|
161
|
+
# as a user turn, so letting them set ``status`` is exactly what flipped the
|
|
162
|
+
# idle bar to "running". Events here only accumulate counters/metadata.
|
|
163
|
+
if isinstance(event, SessionStart):
|
|
164
|
+
if event.model_name:
|
|
165
|
+
self.model = event.model_name
|
|
166
|
+
|
|
167
|
+
elif isinstance(event, LLMCall):
|
|
168
|
+
if event.provider and not self.provider:
|
|
169
|
+
self.provider = event.provider
|
|
170
|
+
if event.model_name and not self.model:
|
|
171
|
+
self.model = event.model_name
|
|
172
|
+
if event.prompt_tokens is not None:
|
|
173
|
+
self.tokens_in += event.prompt_tokens
|
|
174
|
+
if event.completion_tokens is not None:
|
|
175
|
+
self.tokens_out += event.completion_tokens
|
|
176
|
+
if event.token_count is not None:
|
|
177
|
+
self.tokens_total += event.token_count
|
|
178
|
+
if event.cost_estimate is not None:
|
|
179
|
+
self.cost_estimate_total += event.cost_estimate
|
|
180
|
+
|
|
181
|
+
elif isinstance(event, Step):
|
|
182
|
+
self.step = max(self.step, event.step)
|
|
183
|
+
data = event.raw.get("data", {}) or {}
|
|
184
|
+
identity = str(
|
|
185
|
+
data.get("agent_name")
|
|
186
|
+
or event.raw.get("agent_name")
|
|
187
|
+
or data.get("agent_id")
|
|
188
|
+
or event.raw.get("agent_id")
|
|
189
|
+
or ""
|
|
190
|
+
)
|
|
191
|
+
self.last_step_sub_agent = self.is_sub_agent(identity)
|
|
192
|
+
|
|
193
|
+
elif isinstance(event, ToolExec):
|
|
194
|
+
self.tool_calls += 1
|
|
195
|
+
if event.action_name:
|
|
196
|
+
self.last_tool = event.action_name
|
|
197
|
+
|
|
198
|
+
elif isinstance(event, ErrorEvent):
|
|
199
|
+
self.errors += 1
|
|
200
|
+
|
|
201
|
+
elif isinstance(event, IterationDone):
|
|
202
|
+
# No status write — the lifecycle settles the turn on respond() return,
|
|
203
|
+
# not on a feed "done" event (so a planning-only turn still settles).
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
elif isinstance(event, SessionDone):
|
|
207
|
+
# Success/failure colors the turn-summary residue via the renderer's
|
|
208
|
+
# event buffer (turn_failed()), not a status write here.
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
elif isinstance(event, AgentRegistration):
|
|
212
|
+
# First registration = main agent (used for sub-agent grouping).
|
|
213
|
+
# Store both id and name: step events carry only agent_name, so
|
|
214
|
+
# identity comparison must work on names too.
|
|
215
|
+
if not self.main_agent_id and event.agent_id:
|
|
216
|
+
self.main_agent_id = event.agent_id
|
|
217
|
+
if not self.main_agent_name and event.agent_name:
|
|
218
|
+
self.main_agent_name = event.agent_name
|
|
219
|
+
if event.model_name and not self.model:
|
|
220
|
+
self.model = event.model_name
|
|
221
|
+
|
|
222
|
+
elif isinstance(event, AgentEnd):
|
|
223
|
+
# Informational; sub-agent failures bump the error count.
|
|
224
|
+
if event.errors:
|
|
225
|
+
self.errors += len(event.errors)
|
|
226
|
+
|
|
227
|
+
def is_sub_agent(self, identity: str) -> bool:
|
|
228
|
+
"""True if *identity* belongs to a sub-agent (not the main agent).
|
|
229
|
+
|
|
230
|
+
*identity* may be an agent_id OR an agent_name — a match on either
|
|
231
|
+
is treated as "this is the main agent". Step events from the real
|
|
232
|
+
``AgentStepFormatter`` carry only ``agent_name`` (never ``agent_id``),
|
|
233
|
+
so name-based comparison is the primary path.
|
|
234
|
+
|
|
235
|
+
Before any registration is seen (both ``main_agent_id`` and
|
|
236
|
+
``main_agent_name`` empty) nothing is treated as a sub-agent.
|
|
237
|
+
"""
|
|
238
|
+
if not identity:
|
|
239
|
+
return False
|
|
240
|
+
if not self.main_agent_id and not self.main_agent_name:
|
|
241
|
+
return False
|
|
242
|
+
# Match on id OR name — whichever the caller supplies.
|
|
243
|
+
if self.main_agent_id and identity == self.main_agent_id:
|
|
244
|
+
return False
|
|
245
|
+
if self.main_agent_name and identity == self.main_agent_name:
|
|
246
|
+
return False
|
|
247
|
+
return True
|
|
248
|
+
|
|
249
|
+
# ------------------------------------------------------------------
|
|
250
|
+
# Pull: live metrics from the agent's message_manager
|
|
251
|
+
# ------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
def reset_after_clear(self) -> None:
|
|
254
|
+
"""Reset per-conversation live metrics after /clear wiped the history.
|
|
255
|
+
|
|
256
|
+
The context window + step counter are now stale, so zero them (and the
|
|
257
|
+
per-turn tallies). Cumulative session tokens/cost are PRESERVED (real spend)
|
|
258
|
+
so /status still reflects the session's total.
|
|
259
|
+
"""
|
|
260
|
+
self.step = 0
|
|
261
|
+
self.tool_calls = 0
|
|
262
|
+
self.errors = 0
|
|
263
|
+
self.ctx_percent = 0.0
|
|
264
|
+
self.ctx_tokens = 0
|
|
265
|
+
self.compactions = 0
|
|
266
|
+
|
|
267
|
+
def poll(self, agent: Any) -> None:
|
|
268
|
+
"""Pull live context-window metrics from *agent*'s message_manager.
|
|
269
|
+
|
|
270
|
+
*agent* must expose:
|
|
271
|
+
agent.message_manager.get_context_usage_percent() -> float
|
|
272
|
+
agent.message_manager.history.total_tokens -> int
|
|
273
|
+
agent.message_manager.max_input_tokens -> int
|
|
274
|
+
agent.message_manager._compaction_count -> int
|
|
275
|
+
|
|
276
|
+
Missing attributes are silently ignored (the call is best-effort).
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
agent: A running ``Agent`` instance (or any object with the
|
|
280
|
+
expected ``message_manager`` attributes).
|
|
281
|
+
"""
|
|
282
|
+
try:
|
|
283
|
+
mm = agent.message_manager
|
|
284
|
+
except Exception:
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
try:
|
|
288
|
+
self.ctx_percent = mm.get_context_usage_percent()
|
|
289
|
+
except Exception:
|
|
290
|
+
pass
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
self.ctx_tokens = mm.history.total_tokens
|
|
294
|
+
except Exception:
|
|
295
|
+
pass
|
|
296
|
+
|
|
297
|
+
try:
|
|
298
|
+
self.ctx_max = mm.max_input_tokens
|
|
299
|
+
except Exception:
|
|
300
|
+
pass
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
self.compactions = mm._compaction_count
|
|
304
|
+
except Exception:
|
|
305
|
+
pass
|
|
306
|
+
|
|
307
|
+
# ------------------------------------------------------------------
|
|
308
|
+
# Pull: authoritative live tokens/cost from the session's llm_usage dir
|
|
309
|
+
# ------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
def poll_usage(self, session_dir: Path) -> None:
|
|
312
|
+
"""Aggregate tokens + cost from new ``data/llm_usage/*.json`` files.
|
|
313
|
+
|
|
314
|
+
This is the LIVE source of tokens/cost in the local CLI path:
|
|
315
|
+
``capture_llm_usage`` skips the push feed (to avoid double-counting)
|
|
316
|
+
and instead writes one record per LLM call to
|
|
317
|
+
``<session_dir>/data/llm_usage/llm_usage_<ts>.json`` with the same
|
|
318
|
+
shape the formatter uses (``prompt_tokens``, ``completion_tokens``,
|
|
319
|
+
``token_count``, ``cost_estimate``).
|
|
320
|
+
|
|
321
|
+
Each call only reads files not seen on a previous poll, so it is cheap
|
|
322
|
+
to call every turn (and on the toolbar timer). Best-effort: malformed
|
|
323
|
+
or partially-written files are skipped silently.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
session_dir: The session root (containing ``data/llm_usage/``).
|
|
327
|
+
"""
|
|
328
|
+
try:
|
|
329
|
+
usage_dir = Path(session_dir) / "data" / "llm_usage"
|
|
330
|
+
if not usage_dir.is_dir():
|
|
331
|
+
return
|
|
332
|
+
for path in sorted(usage_dir.glob("llm_usage_*.json")):
|
|
333
|
+
name = path.name
|
|
334
|
+
if name in self._seen_usage_files:
|
|
335
|
+
continue
|
|
336
|
+
# Mark as seen up-front: a file that fails to parse once (e.g.
|
|
337
|
+
# mid-write) is unlikely to be the live source of truth, and we
|
|
338
|
+
# never want to re-add its tokens on a later poll.
|
|
339
|
+
self._seen_usage_files.add(name)
|
|
340
|
+
try:
|
|
341
|
+
record = json.loads(path.read_text(encoding="utf-8"))
|
|
342
|
+
except (json.JSONDecodeError, OSError):
|
|
343
|
+
continue
|
|
344
|
+
self._apply_usage_record(record)
|
|
345
|
+
except Exception:
|
|
346
|
+
# poll_usage must never raise into the render loop.
|
|
347
|
+
return
|
|
348
|
+
|
|
349
|
+
def _apply_usage_record(self, record: dict[str, Any]) -> None:
|
|
350
|
+
"""Add one llm_usage record's tokens + cost into the running totals."""
|
|
351
|
+
prompt = record.get("prompt_tokens")
|
|
352
|
+
completion = record.get("completion_tokens")
|
|
353
|
+
total = record.get("token_count")
|
|
354
|
+
cost = record.get("cost_estimate")
|
|
355
|
+
if isinstance(prompt, (int, float)):
|
|
356
|
+
self.tokens_in += int(prompt)
|
|
357
|
+
if isinstance(completion, (int, float)):
|
|
358
|
+
self.tokens_out += int(completion)
|
|
359
|
+
if isinstance(total, (int, float)):
|
|
360
|
+
self.tokens_total += int(total)
|
|
361
|
+
if isinstance(cost, (int, float)):
|
|
362
|
+
self.cost_estimate_total += float(cost)
|
|
363
|
+
provider = record.get("provider")
|
|
364
|
+
if provider and not self.provider:
|
|
365
|
+
self.provider = str(provider)
|
|
366
|
+
model = record.get("model_name")
|
|
367
|
+
if model and not self.model:
|
|
368
|
+
self.model = str(model)
|
|
369
|
+
|
|
370
|
+
# ------------------------------------------------------------------
|
|
371
|
+
# Derived helpers
|
|
372
|
+
# ------------------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
def elapsed(self) -> float:
|
|
375
|
+
"""Seconds elapsed since this ``SessionState`` was constructed."""
|
|
376
|
+
return time.monotonic() - self.started_at
|
cli/ui/statusbar.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""statusbar.py — bottom-toolbar line builder for the POLYROB CLI (Phase 2).
|
|
2
|
+
|
|
3
|
+
Pure builders (no I/O) that turn a ``SessionState`` into the bottom status
|
|
4
|
+
line shown by prompt_toolkit's ``bottom_toolbar``:
|
|
5
|
+
|
|
6
|
+
{model} · {in}↑ {out}↓ · ctx {ctx}% · ${cost} · {elapsed} · {spinner}{status}
|
|
7
|
+
|
|
8
|
+
Two surfaces:
|
|
9
|
+
- ``status_text(state)`` — a plain ``str`` (used by the PlainRenderer and
|
|
10
|
+
by tests; deterministic).
|
|
11
|
+
- ``status_formatted(state, spinner_frame)`` — prompt_toolkit
|
|
12
|
+
``FormattedText`` (used by the live toolbar). Importing prompt_toolkit is
|
|
13
|
+
deferred so the plain path has no hard dependency.
|
|
14
|
+
|
|
15
|
+
The spinner frame is supplied by the caller (the toolbar ticks it on a timer)
|
|
16
|
+
so this module stays pure and time-independent.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import TYPE_CHECKING, List, Tuple
|
|
22
|
+
|
|
23
|
+
from cli.ui.theme import fmt_tokens, ICONS
|
|
24
|
+
from cli.ui.state import SessionState
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING: # pragma: no cover - typing only
|
|
27
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _fmt_elapsed(seconds: float) -> str:
|
|
31
|
+
seconds = max(0.0, seconds)
|
|
32
|
+
if seconds < 60:
|
|
33
|
+
return f"{seconds:.0f}s"
|
|
34
|
+
minutes, secs = divmod(int(seconds), 60)
|
|
35
|
+
if minutes < 60:
|
|
36
|
+
return f"{minutes}m{secs:02d}s"
|
|
37
|
+
hours, minutes = divmod(minutes, 60)
|
|
38
|
+
return f"{hours}h{minutes:02d}m"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _autonomy_active(state: SessionState) -> bool:
|
|
42
|
+
"""True when a background (cron/goal/self-wake) turn is in flight.
|
|
43
|
+
|
|
44
|
+
Orthogonal to the foreground work clock/status — a background turn lights this
|
|
45
|
+
muted ``⟲ autonomy`` token WITHOUT touching the user's work clock or status word
|
|
46
|
+
(a user who walked away never sees a runaway 'working' for autonomous work).
|
|
47
|
+
"""
|
|
48
|
+
lifecycle = getattr(state, "lifecycle", None)
|
|
49
|
+
return bool(lifecycle is not None and lifecycle.autonomy_busy())
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
#: The Claude-Code-style cooking glyph used when no animated spinner is supplied.
|
|
53
|
+
_COOKING_GLYPH = "✱ "
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _is_active(state: SessionState) -> bool:
|
|
57
|
+
"""True when a user turn is in flight (drives the cooking affordance)."""
|
|
58
|
+
lifecycle = getattr(state, "lifecycle", None)
|
|
59
|
+
return bool(lifecycle is not None and lifecycle.is_active())
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def cooking_text(state: SessionState, spinner: str = "") -> str:
|
|
63
|
+
"""The live in-flight affordance: ``✱ cooking… 7s`` (Option A, issue 1).
|
|
64
|
+
|
|
65
|
+
Rendered in the pinned region while the turn runs, replacing the bare status
|
|
66
|
+
word + a separate elapsed segment. The ticking work clock
|
|
67
|
+
(``lifecycle.active_elapsed``, frozen on idle so it shows work time not session
|
|
68
|
+
age) is the liveness; an animated braille *spinner* (supplied by the persistent
|
|
69
|
+
app at 5 Hz) is used as the pulse glyph when available, else a static ``✱``.
|
|
70
|
+
"""
|
|
71
|
+
lifecycle = getattr(state, "lifecycle", None)
|
|
72
|
+
elapsed = lifecycle.active_elapsed() if lifecycle is not None else 0.0
|
|
73
|
+
glyph = spinner if spinner else _COOKING_GLYPH
|
|
74
|
+
return f"{glyph}cooking… {_fmt_elapsed(elapsed)}"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _segments(state: SessionState, spinner: str = "") -> List[str]:
|
|
78
|
+
"""The ordered list of status segments (joined by ' · ')."""
|
|
79
|
+
segs: List[str] = []
|
|
80
|
+
|
|
81
|
+
model = state.model or "—"
|
|
82
|
+
segs.append(model)
|
|
83
|
+
|
|
84
|
+
tok = f"{fmt_tokens(state.tokens_in)}{ICONS.up} {fmt_tokens(state.tokens_out)}{ICONS.down}"
|
|
85
|
+
segs.append(tok)
|
|
86
|
+
|
|
87
|
+
# Live-info (D3): in-flight tool + active sub-agent count. The tool is the
|
|
88
|
+
# CURRENT action — shown only while a turn runs so it doesn't linger at the
|
|
89
|
+
# idle prompt.
|
|
90
|
+
last_tool = getattr(state, "last_tool", "")
|
|
91
|
+
if last_tool and _is_active(state):
|
|
92
|
+
segs.append(f"{ICONS.arrow}{last_tool}")
|
|
93
|
+
n_sub = getattr(state, "subagents_active", 0)
|
|
94
|
+
if n_sub:
|
|
95
|
+
segs.append(f"{n_sub} sub-agent{'s' if n_sub != 1 else ''}")
|
|
96
|
+
|
|
97
|
+
if _autonomy_active(state):
|
|
98
|
+
segs.append("⟲ autonomy")
|
|
99
|
+
|
|
100
|
+
if state.ctx_percent:
|
|
101
|
+
segs.append(f"ctx {state.ctx_percent:.0f}%")
|
|
102
|
+
|
|
103
|
+
segs.append(f"${state.cost_estimate_total:.4f}")
|
|
104
|
+
|
|
105
|
+
# Tail: while a turn runs, ONE Claude-style ``✱ cooking… Xs`` affordance (work
|
|
106
|
+
# clock folded in); when idle, the plain status word (ready/error/stopped) — no
|
|
107
|
+
# clock, so the bar never counts session age at the prompt.
|
|
108
|
+
if _is_active(state):
|
|
109
|
+
segs.append(cooking_text(state, spinner))
|
|
110
|
+
else:
|
|
111
|
+
status = state.status or ""
|
|
112
|
+
word = f"{spinner}{status}".strip()
|
|
113
|
+
if word:
|
|
114
|
+
segs.append(word)
|
|
115
|
+
|
|
116
|
+
return segs
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def autonomy_line(state: SessionState) -> str:
|
|
120
|
+
"""The second pinned line: model/provider + autonomy snapshot (D4).
|
|
121
|
+
|
|
122
|
+
``glm-5.2 · openrouter autonomy: goals 1 · cron 2 · review on`` — built
|
|
123
|
+
from ``state.autonomy_snapshot`` (a slow-polled, cached dict; never read the
|
|
124
|
+
goal/cron SQLite stores on the hot repaint path). Returns "" when there is no
|
|
125
|
+
snapshot, so the line is hidden until autonomy data is available. Pure.
|
|
126
|
+
"""
|
|
127
|
+
snap = getattr(state, "autonomy_snapshot", None)
|
|
128
|
+
if not snap:
|
|
129
|
+
return ""
|
|
130
|
+
auto: List[str] = []
|
|
131
|
+
goals = int(snap.get("goals", 0) or 0)
|
|
132
|
+
cron = int(snap.get("cron", 0) or 0)
|
|
133
|
+
if goals:
|
|
134
|
+
auto.append(f"goals {goals}")
|
|
135
|
+
if cron:
|
|
136
|
+
auto.append(f"cron {cron}")
|
|
137
|
+
if snap.get("review"):
|
|
138
|
+
auto.append("review on")
|
|
139
|
+
left = state.model or "—"
|
|
140
|
+
if state.provider:
|
|
141
|
+
left = f"{left} {ICONS.bullet} {state.provider}"
|
|
142
|
+
if not auto:
|
|
143
|
+
return left
|
|
144
|
+
return f"{left} autonomy: " + f" {ICONS.bullet} ".join(auto)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def status_text(state: SessionState, spinner: str = "") -> str:
|
|
148
|
+
"""Build the plain-text status line (no ANSI)."""
|
|
149
|
+
return f" {ICONS.bullet} ".join(s for s in _segments(state, spinner) if s)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
# prompt_toolkit FormattedText surface
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _status_class(status: str) -> str:
|
|
158
|
+
"""Map a status word to a prompt_toolkit class name for styling.
|
|
159
|
+
|
|
160
|
+
Lifecycle vocabulary is working / ready / error / stopped; legacy words
|
|
161
|
+
(running/completed/done/failed) are still mapped for any injected/test state.
|
|
162
|
+
"""
|
|
163
|
+
s = (status or "").lower()
|
|
164
|
+
if s in ("error", "failed", "stopped"):
|
|
165
|
+
return "class:status.error"
|
|
166
|
+
if s in ("ready", "completed", "done"):
|
|
167
|
+
return "class:status.ok"
|
|
168
|
+
return "class:status.running"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def status_formatted(
|
|
172
|
+
state: SessionState, spinner: str = "", *, include_model: bool = True
|
|
173
|
+
) -> "FormattedText":
|
|
174
|
+
"""Build a prompt_toolkit ``FormattedText`` status toolbar.
|
|
175
|
+
|
|
176
|
+
Each segment carries a class so the toolbar can be themed; the final
|
|
177
|
+
status word is colored by ``_status_class``. ``include_model=False`` omits
|
|
178
|
+
the leading model segment (the framed persistent input shows the model on
|
|
179
|
+
the box's top edge, so the status bar would otherwise repeat it).
|
|
180
|
+
"""
|
|
181
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
182
|
+
|
|
183
|
+
sep = f" {ICONS.bullet} "
|
|
184
|
+
fragments: List[Tuple[str, str]] = [("", " ")]
|
|
185
|
+
|
|
186
|
+
if include_model:
|
|
187
|
+
model = state.model or "—"
|
|
188
|
+
fragments.append(("class:toolbar.model", model))
|
|
189
|
+
fragments.append(("", sep))
|
|
190
|
+
|
|
191
|
+
fragments.append(
|
|
192
|
+
(
|
|
193
|
+
"class:toolbar.tokens",
|
|
194
|
+
f"{fmt_tokens(state.tokens_in)}{ICONS.up} "
|
|
195
|
+
f"{fmt_tokens(state.tokens_out)}{ICONS.down}",
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
fragments.append(("", sep))
|
|
199
|
+
|
|
200
|
+
# Live-info (D3): in-flight tool (only while a turn runs) + sub-agent count.
|
|
201
|
+
last_tool = getattr(state, "last_tool", "")
|
|
202
|
+
if last_tool and _is_active(state):
|
|
203
|
+
fragments.append(("class:toolbar.tool", f"{ICONS.arrow}{last_tool}"))
|
|
204
|
+
fragments.append(("", sep))
|
|
205
|
+
n_sub = getattr(state, "subagents_active", 0)
|
|
206
|
+
if n_sub:
|
|
207
|
+
fragments.append(
|
|
208
|
+
("class:toolbar.subagents", f"{n_sub} sub-agent{'s' if n_sub != 1 else ''}")
|
|
209
|
+
)
|
|
210
|
+
fragments.append(("", sep))
|
|
211
|
+
|
|
212
|
+
if _autonomy_active(state):
|
|
213
|
+
fragments.append(("class:toolbar.autonomy", "⟲ autonomy"))
|
|
214
|
+
fragments.append(("", sep))
|
|
215
|
+
|
|
216
|
+
if state.ctx_percent:
|
|
217
|
+
fragments.append(("class:toolbar.ctx", f"ctx {state.ctx_percent:.0f}%"))
|
|
218
|
+
fragments.append(("", sep))
|
|
219
|
+
|
|
220
|
+
fragments.append(("class:toolbar.cost", f"${state.cost_estimate_total:.4f}"))
|
|
221
|
+
fragments.append(("", sep))
|
|
222
|
+
|
|
223
|
+
if _is_active(state):
|
|
224
|
+
# The live cooking affordance (work clock folded in) — class:toolbar.elapsed
|
|
225
|
+
# so the seconds read as the running clock.
|
|
226
|
+
fragments.append(("class:toolbar.elapsed", cooking_text(state, spinner)))
|
|
227
|
+
else:
|
|
228
|
+
status_word = f"{spinner}{state.status}".strip()
|
|
229
|
+
fragments.append((_status_class(state.status), status_word))
|
|
230
|
+
|
|
231
|
+
return FormattedText(fragments)
|