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/app.py
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
"""app.py — prompt_toolkit PromptSession setup for the POLYROB CLI (Phase 2).
|
|
2
|
+
|
|
3
|
+
Builds the interactive input surface:
|
|
4
|
+
- ``PromptSession`` with ``FileHistory(~/.polyrob/history)``.
|
|
5
|
+
- A ``bottom_toolbar`` callable that pulls live metrics from ``SessionState``
|
|
6
|
+
(via ``statusbar.status_formatted``) and ticks a spinner.
|
|
7
|
+
- Key bindings:
|
|
8
|
+
* ``Enter`` → submit
|
|
9
|
+
* ``Meta+Enter`` (Alt/Esc-Enter) → insert newline (multi-line input)
|
|
10
|
+
* ``Ctrl-C`` → interrupt the current turn (keeps the loop)
|
|
11
|
+
* ``Ctrl-D`` → exit
|
|
12
|
+
- ``prompt_async()`` for the input read (the REPL already runs under asyncio —
|
|
13
|
+
we do NOT start a second loop; proposal §14).
|
|
14
|
+
|
|
15
|
+
Slash autocomplete is Phase 4 — a ``completer`` seam is exposed (param) but no
|
|
16
|
+
registry is built here.
|
|
17
|
+
|
|
18
|
+
Everything is factored so the prompt_toolkit objects can be constructed
|
|
19
|
+
headlessly in tests (no TTY needed); only ``prompt_async`` requires a terminal.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import time
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional
|
|
27
|
+
|
|
28
|
+
from cli.ui import statusbar
|
|
29
|
+
from cli.ui.state import SessionState
|
|
30
|
+
from cli.ui.theme import ICONS, SPINNER_FRAMES
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING: # pragma: no cover - typing only
|
|
33
|
+
from prompt_toolkit import PromptSession
|
|
34
|
+
from prompt_toolkit.completion import Completer
|
|
35
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
from core.paths import polyrob_home
|
|
39
|
+
DEFAULT_HISTORY_PATH = polyrob_home() / "history"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def default_history_path() -> Path:
|
|
43
|
+
"""Return (and ensure the parent of) the default history file path."""
|
|
44
|
+
DEFAULT_HISTORY_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
return DEFAULT_HISTORY_PATH
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Key bindings
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def build_key_bindings() -> "KeyBindings":
|
|
54
|
+
"""Construct the REPL key bindings.
|
|
55
|
+
|
|
56
|
+
- ``Enter`` submits the buffer.
|
|
57
|
+
- ``Meta+Enter`` (``escape, enter``) inserts a newline (multi-line input).
|
|
58
|
+
- ``Ctrl-C`` raises ``KeyboardInterrupt`` so the loop can interrupt the
|
|
59
|
+
current turn without exiting.
|
|
60
|
+
- ``Ctrl-D`` on an empty buffer raises ``EOFError`` to exit.
|
|
61
|
+
"""
|
|
62
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
63
|
+
|
|
64
|
+
kb = KeyBindings()
|
|
65
|
+
|
|
66
|
+
@kb.add("enter")
|
|
67
|
+
def _(event: Any) -> None:
|
|
68
|
+
event.current_buffer.validate_and_handle()
|
|
69
|
+
|
|
70
|
+
@kb.add("escape", "enter")
|
|
71
|
+
def _(event: Any) -> None:
|
|
72
|
+
event.current_buffer.insert_text("\n")
|
|
73
|
+
|
|
74
|
+
@kb.add("c-c")
|
|
75
|
+
def _(event: Any) -> None:
|
|
76
|
+
# Surface as KeyboardInterrupt; the REPL catches it and keeps looping.
|
|
77
|
+
event.app.exit(exception=KeyboardInterrupt)
|
|
78
|
+
|
|
79
|
+
@kb.add("c-d")
|
|
80
|
+
def _(event: Any) -> None:
|
|
81
|
+
if not event.current_buffer.text:
|
|
82
|
+
event.app.exit(exception=EOFError)
|
|
83
|
+
|
|
84
|
+
return kb
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
# Distinctive input frame (prompt fragments + rprompt)
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
#: The left-edge bar glyph shared by the prompt and the bottom toolbar, so the
|
|
92
|
+
#: input region reads as one bordered area (a true 4-sided box isn't possible
|
|
93
|
+
#: for an INLINE prompt_toolkit prompt without going full-screen, which would
|
|
94
|
+
#: forfeit native scrollback — see the CLI review).
|
|
95
|
+
BORDER_BAR = "▌"
|
|
96
|
+
PROMPT_CARET = "❯"
|
|
97
|
+
|
|
98
|
+
#: Max rows the thin input grows to before it stops expanding (a long paste can't
|
|
99
|
+
#: swallow the whole terminal; the buffer still scrolls internally past this).
|
|
100
|
+
MAX_INPUT_ROWS = 10
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def input_height_dimension(line_count: int, max_rows: int = MAX_INPUT_ROWS) -> Any:
|
|
104
|
+
"""Height for the thin growing input: 1 row when empty, +1 per content line,
|
|
105
|
+
clamped to ``max_rows`` (bug B).
|
|
106
|
+
|
|
107
|
+
The input is NOT a fixed-height box — it reserves exactly one row when empty
|
|
108
|
+
and grows as the user types/pastes newlines, the way Claude Code's prompt
|
|
109
|
+
does. Pure (no TTY) so the growth policy is unit-testable.
|
|
110
|
+
"""
|
|
111
|
+
from prompt_toolkit.layout.dimension import Dimension
|
|
112
|
+
|
|
113
|
+
preferred = min(max(1, line_count), max_rows)
|
|
114
|
+
return Dimension(min=1, max=max_rows, preferred=preferred)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def separator_label(state: SessionState) -> str:
|
|
118
|
+
"""The ``rob · model · provider`` label shown on the thin separator rule.
|
|
119
|
+
|
|
120
|
+
Replaces the old 4-sided ``Frame`` title — the agent identity + active model
|
|
121
|
+
sit on the rule above the input instead of a heavy box edge. Pure.
|
|
122
|
+
"""
|
|
123
|
+
from cli.ui.identity import agent_display_name
|
|
124
|
+
name = agent_display_name()
|
|
125
|
+
model = (state.model or "").strip()
|
|
126
|
+
provider = (state.provider or "").strip()
|
|
127
|
+
label = name
|
|
128
|
+
if model:
|
|
129
|
+
label = f"{name} {ICONS.bullet} {model}"
|
|
130
|
+
if provider:
|
|
131
|
+
label = f"{label} {ICONS.bullet} {provider}"
|
|
132
|
+
return label
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def build_prompt_fragments(state: SessionState, *, prompt_text: Optional[str] = None) -> Any:
|
|
136
|
+
"""Build the REPL prompt as themeable prompt_toolkit ``FormattedText``.
|
|
137
|
+
|
|
138
|
+
Default: a distinctive left-border bar + caret (``▌ ❯ ``) so the input line
|
|
139
|
+
is visually distinct from the conversation scrollback. ``prompt_text`` forces
|
|
140
|
+
a literal string (kept for back-compat / tests). Pure — snapshot-testable
|
|
141
|
+
without a TTY.
|
|
142
|
+
"""
|
|
143
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
144
|
+
|
|
145
|
+
if prompt_text is not None:
|
|
146
|
+
return FormattedText([("", prompt_text)])
|
|
147
|
+
return FormattedText(
|
|
148
|
+
[
|
|
149
|
+
("class:prompt.border", BORDER_BAR),
|
|
150
|
+
("class:prompt.caret", f" {PROMPT_CARET} "),
|
|
151
|
+
]
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def build_rprompt(state: SessionState) -> Any:
|
|
156
|
+
"""Build the right-aligned ``model · provider`` affordance (Cursor-style).
|
|
157
|
+
|
|
158
|
+
Returns empty ``FormattedText`` until a model is known (set after the first
|
|
159
|
+
LLM call), so the prompt isn't cluttered at session start. Pure.
|
|
160
|
+
"""
|
|
161
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
162
|
+
|
|
163
|
+
model = (state.model or "").strip()
|
|
164
|
+
provider = (state.provider or "").strip()
|
|
165
|
+
if not model and not provider:
|
|
166
|
+
return FormattedText([])
|
|
167
|
+
label = model or "—"
|
|
168
|
+
if provider:
|
|
169
|
+
label = f"{label} {ICONS.bullet} {provider}"
|
|
170
|
+
return FormattedText([("class:prompt.rprompt", f" {label} ")])
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
# Bottom toolbar
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def make_bottom_toolbar(
|
|
179
|
+
state: SessionState,
|
|
180
|
+
*,
|
|
181
|
+
clock: Callable[[], float] = time.monotonic,
|
|
182
|
+
) -> Callable[[], Any]:
|
|
183
|
+
"""Return a ``bottom_toolbar`` callable that renders live status.
|
|
184
|
+
|
|
185
|
+
The callable is invoked by prompt_toolkit on every repaint; it ticks a
|
|
186
|
+
spinner frame from the monotonic clock and delegates formatting to
|
|
187
|
+
``statusbar.status_formatted``. A leading border bar makes the toolbar read
|
|
188
|
+
as the bottom edge of the framed input region (matching the prompt's bar).
|
|
189
|
+
"""
|
|
190
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
191
|
+
|
|
192
|
+
def _toolbar() -> Any:
|
|
193
|
+
frame_idx = int(clock() * 5) % len(SPINNER_FRAMES)
|
|
194
|
+
spinner = ""
|
|
195
|
+
# Spinner is gated on the lifecycle being active (a turn in flight), NOT on
|
|
196
|
+
# the status string — so it can't animate while idle and survives any
|
|
197
|
+
# status-vocabulary change. Defensive getattr for a lifecycle-less state.
|
|
198
|
+
_lc = getattr(state, "lifecycle", None)
|
|
199
|
+
if _lc is not None and _lc.is_active():
|
|
200
|
+
spinner = SPINNER_FRAMES[frame_idx] + " "
|
|
201
|
+
status = statusbar.status_formatted(state, spinner=spinner)
|
|
202
|
+
return FormattedText(
|
|
203
|
+
[("class:prompt.border", BORDER_BAR)] + list(status)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
return _toolbar
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def toolbar_style():
|
|
210
|
+
"""Return the prompt_toolkit ``Style`` for the bottom toolbar classes."""
|
|
211
|
+
from prompt_toolkit.styles import Style
|
|
212
|
+
|
|
213
|
+
return Style.from_dict(
|
|
214
|
+
{
|
|
215
|
+
"bottom-toolbar": "noreverse",
|
|
216
|
+
"toolbar.model": "bold",
|
|
217
|
+
"toolbar.tokens": "",
|
|
218
|
+
"toolbar.ctx": "",
|
|
219
|
+
"toolbar.cost": "",
|
|
220
|
+
"toolbar.elapsed": "",
|
|
221
|
+
"toolbar.autonomy": "ansibrightblack",
|
|
222
|
+
"status.ok": "ansigreen",
|
|
223
|
+
"status.running": "ansiyellow",
|
|
224
|
+
"status.error": "ansired",
|
|
225
|
+
# Distinctive input frame.
|
|
226
|
+
"prompt.border": "ansicyan bold",
|
|
227
|
+
"prompt.caret": "ansicyan bold",
|
|
228
|
+
"prompt.rprompt": "ansibrightblack",
|
|
229
|
+
"prompt.hint": "ansibrightblack",
|
|
230
|
+
"prompt.frame.title": "ansicyan bold",
|
|
231
|
+
# The bordered input box (opencode-style) — cyan edge, default body.
|
|
232
|
+
"frame.border": "ansicyan",
|
|
233
|
+
"frame.label": "ansicyan bold",
|
|
234
|
+
# Arrow-key model selector (cli/ui/model_selector.py).
|
|
235
|
+
"picker.hint": "ansibrightblack",
|
|
236
|
+
"picker.note": "ansiyellow",
|
|
237
|
+
"picker.group": "ansicyan bold",
|
|
238
|
+
"picker.row": "",
|
|
239
|
+
"picker.sel": "reverse bold",
|
|
240
|
+
}
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# ---------------------------------------------------------------------------
|
|
245
|
+
# PromptSession
|
|
246
|
+
# ---------------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def build_prompt_session(
|
|
250
|
+
state: SessionState,
|
|
251
|
+
*,
|
|
252
|
+
history_path: Optional[Path] = None,
|
|
253
|
+
completer: "Optional[Completer]" = None,
|
|
254
|
+
prompt_text: Optional[str] = None,
|
|
255
|
+
clock: Callable[[], float] = time.monotonic,
|
|
256
|
+
) -> "PromptSession":
|
|
257
|
+
"""Construct the REPL ``PromptSession`` (no TTY required to build it).
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
state: Shared ``SessionState`` (drives prompt/rprompt/toolbar).
|
|
261
|
+
history_path: FileHistory path (default ``~/.polyrob/history``).
|
|
262
|
+
completer: Optional prompt_toolkit ``Completer`` (Phase 4 seam).
|
|
263
|
+
prompt_text: Force a literal prompt prefix; ``None`` (default) uses the
|
|
264
|
+
distinctive framed ``▌ ❯`` prompt + live ``model · provider``
|
|
265
|
+
rprompt.
|
|
266
|
+
clock: Monotonic clock (injectable for tests).
|
|
267
|
+
"""
|
|
268
|
+
from prompt_toolkit import PromptSession
|
|
269
|
+
from prompt_toolkit.history import FileHistory
|
|
270
|
+
|
|
271
|
+
hpath = history_path or default_history_path()
|
|
272
|
+
hpath.parent.mkdir(parents=True, exist_ok=True)
|
|
273
|
+
|
|
274
|
+
# Callables so the prompt frame + rprompt repaint live (the model/provider
|
|
275
|
+
# become known only after the first LLM call).
|
|
276
|
+
return PromptSession(
|
|
277
|
+
message=lambda: build_prompt_fragments(state, prompt_text=prompt_text),
|
|
278
|
+
rprompt=lambda: build_rprompt(state),
|
|
279
|
+
history=FileHistory(str(hpath)),
|
|
280
|
+
key_bindings=build_key_bindings(),
|
|
281
|
+
bottom_toolbar=make_bottom_toolbar(state, clock=clock),
|
|
282
|
+
completer=completer,
|
|
283
|
+
complete_while_typing=False,
|
|
284
|
+
multiline=False,
|
|
285
|
+
style=toolbar_style(),
|
|
286
|
+
refresh_interval=0.2, # ~5 Hz toolbar repaint for the spinner/elapsed
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def make_prompt_reader(session: "PromptSession") -> Callable[[], Any]:
|
|
291
|
+
"""Adapt a ``PromptSession`` into the REPL's ``read_line`` async seam.
|
|
292
|
+
|
|
293
|
+
Returns a zero-arg coroutine that reads one line via ``prompt_async`` and
|
|
294
|
+
raises ``EOFError`` on Ctrl-D (so the REPL loop breaks cleanly).
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
async def read_line() -> str:
|
|
298
|
+
try:
|
|
299
|
+
return await session.prompt_async()
|
|
300
|
+
except EOFError:
|
|
301
|
+
raise
|
|
302
|
+
# KeyboardInterrupt propagates to the loop, which keeps looping.
|
|
303
|
+
|
|
304
|
+
return read_line
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# ---------------------------------------------------------------------------
|
|
308
|
+
# Persistent bottom-anchored Application (D4/D5 — gated POLYROB_PERSISTENT_INPUT)
|
|
309
|
+
# ---------------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def persistent_input_enabled() -> bool:
|
|
313
|
+
"""True when the persistent bottom-anchored input is active (default ON).
|
|
314
|
+
|
|
315
|
+
The persistent ``Application`` keeps the input box PINNED at the bottom and
|
|
316
|
+
LIVE during a turn (status/spinner repaint, tool output scrolls above) — the
|
|
317
|
+
Claude-Code / opencode experience. It is the default for an interactive TTY;
|
|
318
|
+
set ``POLYROB_PERSISTENT_INPUT=0`` (or ``off``/``false``/``no``) to force the
|
|
319
|
+
legacy ephemeral ``prompt_async`` path. Non-TTY / ``--plain`` always use the
|
|
320
|
+
legacy path regardless (the caller gates on ``is_tty`` + ``not plain``), and a
|
|
321
|
+
build/start failure fails open to it too.
|
|
322
|
+
"""
|
|
323
|
+
import os
|
|
324
|
+
|
|
325
|
+
return os.environ.get("POLYROB_PERSISTENT_INPUT", "1").strip().lower() not in (
|
|
326
|
+
"0", "false", "no", "off", "",
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def build_app(
|
|
331
|
+
state: SessionState,
|
|
332
|
+
*,
|
|
333
|
+
on_submit: Callable[[str], Any],
|
|
334
|
+
completer: "Optional[Completer]" = None,
|
|
335
|
+
on_interrupt: Optional[Callable[[], None]] = None,
|
|
336
|
+
clock: Callable[[], float] = time.monotonic,
|
|
337
|
+
output: Any = None,
|
|
338
|
+
input: Any = None,
|
|
339
|
+
) -> Any:
|
|
340
|
+
"""Build the persistent, NON-full-screen bottom-anchored ``Application``.
|
|
341
|
+
|
|
342
|
+
The layout reserves only the bottom region — an input window (``▌ ❯``), a live
|
|
343
|
+
status window, and a conditional autonomy window. Content (agent messages,
|
|
344
|
+
summaries) keeps scrolling natively ABOVE via ``run_in_terminal`` (so native
|
|
345
|
+
scrollback is preserved — this is NOT a full-screen transcript app). Because
|
|
346
|
+
the ``Application`` runs continuously, the status repaints LIVE during a turn
|
|
347
|
+
(the toolbar-freeze is gone).
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
state: Shared ``SessionState`` (drives the status/autonomy lines).
|
|
351
|
+
on_submit: ``(text) -> None`` called on Enter for a non-blank line. The
|
|
352
|
+
caller schedules the turn as a background task.
|
|
353
|
+
completer: Optional slash completer.
|
|
354
|
+
on_interrupt: Optional ``() -> None`` called on Ctrl-C (cancel the turn).
|
|
355
|
+
clock: Monotonic clock (spinner tick; injectable for tests).
|
|
356
|
+
output/input: prompt_toolkit Output/Input (DummyOutput/pipe in tests).
|
|
357
|
+
|
|
358
|
+
Returns ``(app, input_buffer)``. Buildable headlessly (no TTY); ``app.run_async``
|
|
359
|
+
requires a terminal.
|
|
360
|
+
"""
|
|
361
|
+
from prompt_toolkit.application import Application
|
|
362
|
+
from prompt_toolkit.buffer import Buffer
|
|
363
|
+
from prompt_toolkit.filters import Condition
|
|
364
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
365
|
+
from prompt_toolkit.key_binding import merge_key_bindings
|
|
366
|
+
from prompt_toolkit.layout import HSplit, Layout, VSplit, Window
|
|
367
|
+
from prompt_toolkit.layout.containers import ConditionalContainer
|
|
368
|
+
from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl
|
|
369
|
+
|
|
370
|
+
from cli.ui.model_selector import ReplPicker
|
|
371
|
+
|
|
372
|
+
def _accept(buff: Any) -> bool:
|
|
373
|
+
text = buff.text
|
|
374
|
+
if text.strip():
|
|
375
|
+
on_submit(text)
|
|
376
|
+
buff.reset() # clear the input after submit
|
|
377
|
+
return False # don't keep the text in the buffer
|
|
378
|
+
|
|
379
|
+
input_buffer = Buffer(
|
|
380
|
+
multiline=False,
|
|
381
|
+
accept_handler=_accept,
|
|
382
|
+
completer=completer,
|
|
383
|
+
complete_while_typing=False,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# The arrow-key model selector (/model) renders as a conditional list ABOVE
|
|
387
|
+
# the input and borrows this buffer as its search field while open. It runs
|
|
388
|
+
# entirely on the event loop (resolved via a Future), so it never blocks the
|
|
389
|
+
# REPL or routes through patch_stdout's StdoutProxy (the old picker's bug).
|
|
390
|
+
picker = ReplPicker(input_buffer)
|
|
391
|
+
input_buffer.on_text_changed += lambda _: picker.on_search_changed()
|
|
392
|
+
|
|
393
|
+
def _caret_prefix() -> Any:
|
|
394
|
+
# The ❯ caret prefixes every input row (thin design — no box border).
|
|
395
|
+
return FormattedText([("class:prompt.caret", f"{PROMPT_CARET} ")])
|
|
396
|
+
|
|
397
|
+
def _separator_label() -> Any:
|
|
398
|
+
# The agent name + active model sit on the thin rule above the input —
|
|
399
|
+
# the heavy 4-sided Frame is gone (bug B).
|
|
400
|
+
return FormattedText(
|
|
401
|
+
[("class:prompt.frame.title", f"─ {separator_label(state)} ")]
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
def _input_height() -> Any:
|
|
405
|
+
# 1 row when empty; grows with typed/pasted newlines; clamped.
|
|
406
|
+
return input_height_dimension(input_buffer.document.line_count)
|
|
407
|
+
|
|
408
|
+
def _status() -> Any:
|
|
409
|
+
frame_idx = int(clock() * 5) % len(SPINNER_FRAMES)
|
|
410
|
+
spinner = ""
|
|
411
|
+
# Spinner is gated on the lifecycle being active (a turn in flight), NOT on
|
|
412
|
+
# the status string — so it can't animate while idle and survives any
|
|
413
|
+
# status-vocabulary change. Defensive getattr for a lifecycle-less state.
|
|
414
|
+
_lc = getattr(state, "lifecycle", None)
|
|
415
|
+
if _lc is not None and _lc.is_active():
|
|
416
|
+
spinner = SPINNER_FRAMES[frame_idx] + " "
|
|
417
|
+
# Model lives on the box's top edge (the frame title), so omit it here.
|
|
418
|
+
status = statusbar.status_formatted(state, spinner=spinner, include_model=False)
|
|
419
|
+
# Indent one space so the status bar aligns under the box body.
|
|
420
|
+
return FormattedText([("", " ")] + list(status))
|
|
421
|
+
|
|
422
|
+
def _hint() -> Any:
|
|
423
|
+
# A quiet keybinding hint line (opencode/Claude-Code parity).
|
|
424
|
+
return FormattedText(
|
|
425
|
+
[("class:prompt.hint",
|
|
426
|
+
f" ⏎ send {ICONS.bullet} ⌥⏎ newline {ICONS.bullet} ^C stop "
|
|
427
|
+
f"{ICONS.bullet} /help")]
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
input_window = Window(
|
|
431
|
+
BufferControl(buffer=input_buffer, input_processors=[]),
|
|
432
|
+
get_line_prefix=lambda *a, **k: _caret_prefix(),
|
|
433
|
+
height=_input_height,
|
|
434
|
+
# Without this the input window is GREEDY: in the bottom region it absorbs
|
|
435
|
+
# all free vertical space up to its max, pushing the status/hint rows to
|
|
436
|
+
# the far bottom with a huge empty gap (the "stretched input" bug). Pin it
|
|
437
|
+
# to its content height so the region stays compact and only grows as the
|
|
438
|
+
# user types/pastes.
|
|
439
|
+
dont_extend_height=True,
|
|
440
|
+
wrap_lines=True,
|
|
441
|
+
)
|
|
442
|
+
# A thin titled rule above the input: ``─ rob · model · provider ───────``.
|
|
443
|
+
separator = VSplit(
|
|
444
|
+
[
|
|
445
|
+
Window(
|
|
446
|
+
FormattedTextControl(_separator_label),
|
|
447
|
+
height=1,
|
|
448
|
+
dont_extend_width=True,
|
|
449
|
+
),
|
|
450
|
+
Window(char="─", height=1, style="class:prompt.border"),
|
|
451
|
+
]
|
|
452
|
+
)
|
|
453
|
+
status_window = Window(FormattedTextControl(_status), height=1)
|
|
454
|
+
hint_window = Window(FormattedTextControl(_hint), height=1)
|
|
455
|
+
autonomy_window = ConditionalContainer(
|
|
456
|
+
Window(
|
|
457
|
+
FormattedTextControl(
|
|
458
|
+
lambda: FormattedText(
|
|
459
|
+
[("", " "), ("class:prompt.rprompt", statusbar.autonomy_line(state))]
|
|
460
|
+
)
|
|
461
|
+
),
|
|
462
|
+
height=1,
|
|
463
|
+
),
|
|
464
|
+
filter=Condition(lambda: bool(statusbar.autonomy_line(state))),
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
layout = Layout(
|
|
468
|
+
HSplit([
|
|
469
|
+
picker.container(),
|
|
470
|
+
separator,
|
|
471
|
+
input_window,
|
|
472
|
+
status_window,
|
|
473
|
+
autonomy_window,
|
|
474
|
+
hint_window,
|
|
475
|
+
])
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
kb = merge_key_bindings([
|
|
479
|
+
_persistent_key_bindings(on_interrupt=on_interrupt, is_picker_active=picker.is_active),
|
|
480
|
+
picker.key_bindings(),
|
|
481
|
+
])
|
|
482
|
+
|
|
483
|
+
app = Application(
|
|
484
|
+
layout=layout,
|
|
485
|
+
key_bindings=kb,
|
|
486
|
+
style=toolbar_style(),
|
|
487
|
+
full_screen=False,
|
|
488
|
+
refresh_interval=0.2,
|
|
489
|
+
erase_when_done=True,
|
|
490
|
+
output=output,
|
|
491
|
+
input=input,
|
|
492
|
+
)
|
|
493
|
+
picker.app = app
|
|
494
|
+
app._picker = picker # /model reaches the picker via get_app_or_none()._picker
|
|
495
|
+
return app, input_buffer
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def _persistent_key_bindings(*, on_interrupt: Optional[Callable[[], None]] = None,
|
|
499
|
+
is_picker_active: Optional[Callable[[], bool]] = None) -> Any:
|
|
500
|
+
"""Key bindings for the persistent Application.
|
|
501
|
+
|
|
502
|
+
- ``Enter`` submits (the buffer's accept handler runs ``on_submit``).
|
|
503
|
+
- ``Meta+Enter`` inserts a newline (multi-line input).
|
|
504
|
+
- ``Ctrl-C`` calls ``on_interrupt`` (cancel the in-flight turn) without exiting.
|
|
505
|
+
- ``Ctrl-D`` on an empty buffer exits the app (EOF).
|
|
506
|
+
|
|
507
|
+
``is_picker_active`` gates every binding on ``~active`` so that while the
|
|
508
|
+
arrow-key model selector is open, Enter/Ctrl-C/Ctrl-D route to the picker's
|
|
509
|
+
own bindings (submit/cancel select a model, not the turn) rather than here.
|
|
510
|
+
Typed characters still flow into the shared buffer — that IS the picker's
|
|
511
|
+
search field.
|
|
512
|
+
"""
|
|
513
|
+
from prompt_toolkit.filters import Condition
|
|
514
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
515
|
+
|
|
516
|
+
kb = KeyBindings()
|
|
517
|
+
_pa = is_picker_active or (lambda: False)
|
|
518
|
+
not_picking = Condition(lambda: not _pa())
|
|
519
|
+
|
|
520
|
+
@kb.add("enter", filter=not_picking)
|
|
521
|
+
def _(event: Any) -> None:
|
|
522
|
+
event.current_buffer.validate_and_handle()
|
|
523
|
+
|
|
524
|
+
@kb.add("escape", "enter", filter=not_picking)
|
|
525
|
+
def _(event: Any) -> None:
|
|
526
|
+
event.current_buffer.insert_text("\n")
|
|
527
|
+
|
|
528
|
+
@kb.add("c-c", filter=not_picking)
|
|
529
|
+
def _(event: Any) -> None:
|
|
530
|
+
if on_interrupt is not None:
|
|
531
|
+
on_interrupt()
|
|
532
|
+
|
|
533
|
+
@kb.add("c-d", filter=not_picking)
|
|
534
|
+
def _(event: Any) -> None:
|
|
535
|
+
if not event.current_buffer.text:
|
|
536
|
+
event.app.exit(exception=EOFError)
|
|
537
|
+
|
|
538
|
+
return kb
|