aria-code 0.167.0__py3-none-win_amd64.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.
- aria_bots/__init__.py +7 -0
- aria_bots/channels/README.md +97 -0
- aria_bots/channels/__init__.py +1 -0
- aria_bots/channels/approvals.py +263 -0
- aria_bots/channels/conversation.py +280 -0
- aria_bots/channels/digest.py +186 -0
- aria_bots/channels/registry.py +95 -0
- aria_bots/engine.py +144 -0
- aria_bots/feishu_bot.py +1385 -0
- aria_bots/logistics/__init__.py +1 -0
- aria_bots/logistics/carriers.py +253 -0
- aria_bots/logistics/freight.py +154 -0
- aria_bots/logistics/inventory.py +242 -0
- aria_bots/logistics/mcp_server.py +156 -0
- aria_bots/logistics/records.py +119 -0
- aria_bots/logistics/tenancy.py +152 -0
- aria_bots/market.py +91 -0
- aria_bots/relay_client.py +326 -0
- aria_bots/telegram_bot.py +299 -0
- aria_code/__init__.py +111 -0
- aria_code/_bin/aria-native.exe +0 -0
- aria_code/_bin/aria-runtime.exe +0 -0
- aria_code/_version.py +20 -0
- aria_code/account_memory.py +296 -0
- aria_code/account_session.py +166 -0
- aria_code/adk_apps/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/agent.py +32 -0
- aria_code/adk_apps/aria_research/__init__.py +1 -0
- aria_code/adk_apps/aria_research/agent.py +31 -0
- aria_code/agents/__init__.py +49 -0
- aria_code/agents/base.py +329 -0
- aria_code/agents/code_review.py +200 -0
- aria_code/agents/deep/__init__.py +37 -0
- aria_code/agents/deep/calibration_loop.py +144 -0
- aria_code/agents/deep/critic.py +125 -0
- aria_code/agents/deep/deepen.py +193 -0
- aria_code/agents/deep/models.py +149 -0
- aria_code/agents/deep/pipeline.py +164 -0
- aria_code/agents/deep/quant_fusion.py +192 -0
- aria_code/agents/deep/themes.py +95 -0
- aria_code/agents/deep/tiers.py +106 -0
- aria_code/agents/engineering/__init__.py +14 -0
- aria_code/agents/engineering/coder.py +222 -0
- aria_code/agents/engineering/tester.py +145 -0
- aria_code/agents/financial/__init__.py +21 -0
- aria_code/agents/financial/cashflow_burn.py +119 -0
- aria_code/agents/financial/catalyst.py +279 -0
- aria_code/agents/financial/corporate_finance.py +175 -0
- aria_code/agents/financial/debate.py +224 -0
- aria_code/agents/financial/earnings.py +315 -0
- aria_code/agents/financial/fundamental.py +178 -0
- aria_code/agents/financial/macro.py +111 -0
- aria_code/agents/financial/news.py +231 -0
- aria_code/agents/financial/risk.py +146 -0
- aria_code/agents/financial/sector.py +279 -0
- aria_code/agents/financial/strategist.py +121 -0
- aria_code/agents/financial/stripe_revenue.py +127 -0
- aria_code/agents/financial/synthesis.py +274 -0
- aria_code/agents/financial/technical.py +277 -0
- aria_code/agents/orchestrator.py +44 -0
- aria_code/agents/portfolio_agent.py +392 -0
- aria_code/agents/realty/__init__.py +62 -0
- aria_code/agents/realty/asset_diagnosis.py +152 -0
- aria_code/agents/realty/business_match.py +166 -0
- aria_code/agents/realty/cashflow_verify.py +209 -0
- aria_code/agents/realty/contract_rules.py +210 -0
- aria_code/agents/realty/energy_anomaly.py +189 -0
- aria_code/agents/realty/exit_settlement.py +208 -0
- aria_code/agents/realty/fulfillment_risk.py +206 -0
- aria_code/agents/realty/ops_optimize.py +160 -0
- aria_code/agents/realty/revenue_share.py +215 -0
- aria_code/agents/registry.py +168 -0
- aria_code/agents/signal_scheme.py +111 -0
- aria_code/agents/sports/__init__.py +0 -0
- aria_code/agents/sports/football_agent.py +197 -0
- aria_code/agents/supervisor.py +113 -0
- aria_code/agents/team.py +500 -0
- aria_code/agents/video/__init__.py +16 -0
- aria_code/agents/video/director.py +376 -0
- aria_code/agents/video/qa_agent.py +143 -0
- aria_code/agents/video/script_doctor.py +112 -0
- aria_code/agents/warehouse/__init__.py +25 -0
- aria_code/agents/warehouse/contracts.py +26 -0
- aria_code/agents/warehouse/dashboard.py +155 -0
- aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
- aria_code/agents/warehouse/inbound_exceptions.py +25 -0
- aria_code/agents/warehouse/inventory_health.py +24 -0
- aria_code/agents/warehouse/local_run.py +71 -0
- aria_code/agents/warehouse/local_snapshot.py +132 -0
- aria_code/agents/warehouse/logistics_cost.py +84 -0
- aria_code/agents/warehouse/logistics_sync.py +21 -0
- aria_code/agents/warehouse/navigation.schema.json +110 -0
- aria_code/agents/warehouse/task_agent.py +172 -0
- aria_code/agents/warehouse/workflow.py +31 -0
- aria_code/aliyun_data_client.py +4 -0
- aria_code/apps/README.md +12 -0
- aria_code/apps/__init__.py +2 -0
- aria_code/apps/channels/README.md +9 -0
- aria_code/apps/channels/__init__.py +17 -0
- aria_code/apps/channels/approvals.py +11 -0
- aria_code/apps/channels/conversation.py +11 -0
- aria_code/apps/channels/digest.py +11 -0
- aria_code/apps/channels/intake.py +92 -0
- aria_code/apps/channels/registry.py +11 -0
- aria_code/apps/channels/tradingview.py +94 -0
- aria_code/apps/cli/README.md +13 -0
- aria_code/apps/cli/__init__.py +2 -0
- aria_code/apps/cli/_optional.py +120 -0
- aria_code/apps/cli/app_server.py +656 -0
- aria_code/apps/cli/auto_update.py +125 -0
- aria_code/apps/cli/bootstrap.py +206 -0
- aria_code/apps/cli/broker_render.py +206 -0
- aria_code/apps/cli/chat_turn.py +1327 -0
- aria_code/apps/cli/cloud_models.py +231 -0
- aria_code/apps/cli/codegen_paths.py +29 -0
- aria_code/apps/cli/commands/__init__.py +34 -0
- aria_code/apps/cli/commands/_business_render.py +78 -0
- aria_code/apps/cli/commands/_ui.py +71 -0
- aria_code/apps/cli/commands/analysis_cmds.py +378 -0
- aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
- aria_code/apps/cli/commands/auth_cmds.py +215 -0
- aria_code/apps/cli/commands/backtest_cmds.py +2518 -0
- aria_code/apps/cli/commands/broker_cmds.py +1236 -0
- aria_code/apps/cli/commands/business_workflow_cmds.py +379 -0
- aria_code/apps/cli/commands/canvas_cmds.py +77 -0
- aria_code/apps/cli/commands/catalog.py +189 -0
- aria_code/apps/cli/commands/core_cmds.py +2748 -0
- aria_code/apps/cli/commands/data_cmds.py +521 -0
- aria_code/apps/cli/commands/diagnostic_cmds.py +285 -0
- aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
- aria_code/apps/cli/commands/engineering_cmds.py +399 -0
- aria_code/apps/cli/commands/file_cmds.py +312 -0
- aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
- aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
- aria_code/apps/cli/commands/market.py +544 -0
- aria_code/apps/cli/commands/market_cmds.py +1419 -0
- aria_code/apps/cli/commands/market_context.py +432 -0
- aria_code/apps/cli/commands/market_render.py +7 -0
- aria_code/apps/cli/commands/model_cmds.py +2374 -0
- aria_code/apps/cli/commands/ops_cmds.py +721 -0
- aria_code/apps/cli/commands/orchestrator_cmds.py +181 -0
- aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
- aria_code/apps/cli/commands/portfolio_cmds.py +1316 -0
- aria_code/apps/cli/commands/report.py +476 -0
- aria_code/apps/cli/commands/scaffold_templates.py +617 -0
- aria_code/apps/cli/commands/session_cmds.py +236 -0
- aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
- aria_code/apps/cli/commands/team.py +723 -0
- aria_code/apps/cli/commands/team_render.py +8 -0
- aria_code/apps/cli/commands/ui_cmds.py +401 -0
- aria_code/apps/cli/commands/warehouse_cmds.py +297 -0
- aria_code/apps/cli/commands/workflow_cmds.py +629 -0
- aria_code/apps/cli/commands/workspace_cmds.py +1543 -0
- aria_code/apps/cli/config_paths.py +73 -0
- aria_code/apps/cli/config_store.py +158 -0
- aria_code/apps/cli/constants.py +88 -0
- aria_code/apps/cli/context.py +34 -0
- aria_code/apps/cli/conversation_policy.py +53 -0
- aria_code/apps/cli/deterministic.py +200 -0
- aria_code/apps/cli/direct.py +48 -0
- aria_code/apps/cli/earnings_view.py +172 -0
- aria_code/apps/cli/endpoint_migration.py +64 -0
- aria_code/apps/cli/exec_events.py +255 -0
- aria_code/apps/cli/file_references.py +155 -0
- aria_code/apps/cli/finance_service_catalog.py +111 -0
- aria_code/apps/cli/football_reports.py +231 -0
- aria_code/apps/cli/github_app_auth.py +135 -0
- aria_code/apps/cli/google_login.py +239 -0
- aria_code/apps/cli/handlers/__init__.py +11 -0
- aria_code/apps/cli/handlers/broker_handlers.py +122 -0
- aria_code/apps/cli/handlers/chart_handlers.py +1314 -0
- aria_code/apps/cli/handlers/market_handlers.py +2570 -0
- aria_code/apps/cli/handlers/realty_handlers.py +114 -0
- aria_code/apps/cli/handlers/sports_handlers.py +22 -0
- aria_code/apps/cli/handlers/strategy_advice.py +82 -0
- aria_code/apps/cli/headless.py +352 -0
- aria_code/apps/cli/helpers.py +326 -0
- aria_code/apps/cli/hooks.py +180 -0
- aria_code/apps/cli/i18n.py +310 -0
- aria_code/apps/cli/intent.py +136 -0
- aria_code/apps/cli/intent_router.py +305 -0
- aria_code/apps/cli/intent_shadow.py +72 -0
- aria_code/apps/cli/intent_signals.py +347 -0
- aria_code/apps/cli/lifecycle_hooks.py +48 -0
- aria_code/apps/cli/local_tool_schemas.py +550 -0
- aria_code/apps/cli/main.py +39 -0
- aria_code/apps/cli/market_hours.py +100 -0
- aria_code/apps/cli/market_metadata.py +135 -0
- aria_code/apps/cli/market_universe.py +487 -0
- aria_code/apps/cli/message_processing.py +310 -0
- aria_code/apps/cli/mixin_binding.py +32 -0
- aria_code/apps/cli/model_catalog.py +652 -0
- aria_code/apps/cli/model_probe.py +169 -0
- aria_code/apps/cli/model_retirement.py +65 -0
- aria_code/apps/cli/native_branding.py +12 -0
- aria_code/apps/cli/native_bridge.py +118 -0
- aria_code/apps/cli/native_entry.py +58 -0
- aria_code/apps/cli/pdf_report.py +977 -0
- aria_code/apps/cli/plan_mode.py +139 -0
- aria_code/apps/cli/plotly_html.py +15 -0
- aria_code/apps/cli/prediction_feedback.py +202 -0
- aria_code/apps/cli/preflight.py +502 -0
- aria_code/apps/cli/project_aria.py +60 -0
- aria_code/apps/cli/prompt_assembly.py +78 -0
- aria_code/apps/cli/prompts/__init__.py +0 -0
- aria_code/apps/cli/prompts/coding.py +71 -0
- aria_code/apps/cli/prompts/select.py +193 -0
- aria_code/apps/cli/prompts/system_prompts.py +562 -0
- aria_code/apps/cli/prompts/ui.py +436 -0
- aria_code/apps/cli/provider_endpoints.py +237 -0
- aria_code/apps/cli/provider_keys.py +73 -0
- aria_code/apps/cli/providers/__init__.py +1 -0
- aria_code/apps/cli/providers/base.py +775 -0
- aria_code/apps/cli/providers/chat_routing.py +139 -0
- aria_code/apps/cli/providers/collaboration.py +98 -0
- aria_code/apps/cli/providers/llm/__init__.py +1 -0
- aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
- aria_code/apps/cli/providers/llm/ollama_stream.py +1574 -0
- aria_code/apps/cli/providers/llm/response_cache.py +60 -0
- aria_code/apps/cli/providers/llm/sse_stream.py +298 -0
- aria_code/apps/cli/providers/runtime_bridge.py +586 -0
- aria_code/apps/cli/providers/vertexai_stream.py +592 -0
- aria_code/apps/cli/release_download.py +159 -0
- aria_code/apps/cli/review_runner.py +111 -0
- aria_code/apps/cli/runtime_consumer.py +769 -0
- aria_code/apps/cli/sandbox.py +32 -0
- aria_code/apps/cli/session_export.py +120 -0
- aria_code/apps/cli/session_jsonl.py +208 -0
- aria_code/apps/cli/session_refresh.py +123 -0
- aria_code/apps/cli/session_store.py +125 -0
- aria_code/apps/cli/skills_catalog.py +273 -0
- aria_code/apps/cli/task_isolation.py +211 -0
- aria_code/apps/cli/terminal_chrome.py +412 -0
- aria_code/apps/cli/todo_tracker.py +190 -0
- aria_code/apps/cli/tool_executor.py +932 -0
- aria_code/apps/cli/tool_registry.py +34 -0
- aria_code/apps/cli/tool_scope.py +113 -0
- aria_code/apps/cli/tools/__init__.py +40 -0
- aria_code/apps/cli/tools/context.py +46 -0
- aria_code/apps/cli/tools/file_tools.py +181 -0
- aria_code/apps/cli/tools/market_tools.py +626 -0
- aria_code/apps/cli/tools/notebook_tools.py +111 -0
- aria_code/apps/cli/tools/system_tools.py +656 -0
- aria_code/apps/cli/tools/write_tools.py +961 -0
- aria_code/apps/cli/tradingview_bridge.py +529 -0
- aria_code/apps/cli/transactions.py +269 -0
- aria_code/apps/cli/turn_planning.py +137 -0
- aria_code/apps/cli/update_check.py +257 -0
- aria_code/apps/cli/updater.py +373 -0
- aria_code/apps/cli/utils/__init__.py +0 -0
- aria_code/apps/cli/utils/market_detect.py +1663 -0
- aria_code/apps/cli/web_login.py +136 -0
- aria_code/apps/cli/workflow_runner.py +127 -0
- aria_code/apps/cli/workspace_route.py +121 -0
- aria_code/apps/daemon/README.md +14 -0
- aria_code/apps/vscode/README.md +115 -0
- aria_code/apps/vscode/package.json +70 -0
- aria_code/aria_cli.py +5244 -0
- aria_code/aria_daemon.py +1372 -0
- aria_code/aria_feishu_bot.py +16 -0
- aria_code/aria_mcp_server.py +61 -0
- aria_code/aria_relay_client.py +4 -0
- aria_code/aria_telegram_bot.py +11 -0
- aria_code/ariarc.py +385 -0
- aria_code/artifacts.py +581 -0
- aria_code/backtest_engine.py +4 -0
- aria_code/backtest_report.py +4 -0
- aria_code/brokers/__init__.py +78 -0
- aria_code/brokers/automation.py +193 -0
- aria_code/brokers/base.py +231 -0
- aria_code/brokers/capabilities.py +264 -0
- aria_code/brokers/cn/__init__.py +10 -0
- aria_code/brokers/cn/easytrader_broker.py +193 -0
- aria_code/brokers/cn/futu_broker.py +194 -0
- aria_code/brokers/cn/longbridge_broker.py +190 -0
- aria_code/brokers/cn/tiger_broker.py +196 -0
- aria_code/brokers/cn/xtquant_broker.py +225 -0
- aria_code/brokers/config.py +414 -0
- aria_code/brokers/intl/__init__.py +5 -0
- aria_code/brokers/intl/alpaca_broker.py +183 -0
- aria_code/brokers/intl/ibkr_broker.py +215 -0
- aria_code/brokers/intl/trading212_broker.py +183 -0
- aria_code/brokers/intl/webull_broker.py +156 -0
- aria_code/brokers/paper_broker.py +266 -0
- aria_code/brokers/planning.py +296 -0
- aria_code/brokers/registry.py +213 -0
- aria_code/brokers/trading.py +267 -0
- aria_code/canva_client.py +4 -0
- aria_code/change_store.py +127 -0
- aria_code/clients/__init__.py +0 -0
- aria_code/clients/aliyun_data_client.py +660 -0
- aria_code/clients/aria_relay_client.py +16 -0
- aria_code/clients/canva_client.py +284 -0
- aria_code/clients/figma_client.py +100 -0
- aria_code/clients/football_data_client.py +1671 -0
- aria_code/clients/kling_video_client.py +182 -0
- aria_code/clients/market_data_client.py +1985 -0
- aria_code/clients/openai_image_client.py +242 -0
- aria_code/clients/runway_video_client.py +157 -0
- aria_code/clients/warehouse_erp_client.py +105 -0
- aria_code/cloud_memory.py +190 -0
- aria_code/command_safety.py +19 -0
- aria_code/computer_use_server.py +276 -0
- aria_code/computer_use_tools.py +4 -0
- aria_code/dashboard_generator.py +579 -0
- aria_code/data_analysis_tools.py +4 -0
- aria_code/data_cleaner.py +483 -0
- aria_code/data_service.py +513 -0
- aria_code/datasources/__init__.py +23 -0
- aria_code/datasources/base.py +166 -0
- aria_code/datasources/router.py +253 -0
- aria_code/datasources/sec_statements.py +217 -0
- aria_code/datasources/sources/__init__.py +15 -0
- aria_code/datasources/sources/akshare_source.py +269 -0
- aria_code/datasources/sources/alpha_vantage_source.py +203 -0
- aria_code/datasources/sources/edgar_source.py +219 -0
- aria_code/datasources/sources/finnhub_source.py +198 -0
- aria_code/datasources/sources/fred_source.py +223 -0
- aria_code/datasources/sources/tushare_source.py +143 -0
- aria_code/datasources/sources/web_scraper_source.py +278 -0
- aria_code/datasources/sources/world_bank_source.py +206 -0
- aria_code/datasources/sources/yfinance_source.py +152 -0
- aria_code/doctor.py +860 -0
- aria_code/domain/__init__.py +0 -0
- aria_code/domain/_finance_core/__init__.py +5 -0
- aria_code/domain/_finance_core/backtest_engine.py +325 -0
- aria_code/domain/_finance_core/performance_stats.py +101 -0
- aria_code/domain/backtest_engine.py +75 -0
- aria_code/domain/backtest_report.py +488 -0
- aria_code/domain/finance_formulas.py +389 -0
- aria_code/domain/performance_stats.py +6 -0
- aria_code/domain/portfolio_ledger.py +307 -0
- aria_code/domain/strategy_vault.py +421 -0
- aria_code/evals/__init__.py +37 -0
- aria_code/evals/behavior.py +113 -0
- aria_code/evals/gap.py +93 -0
- aria_code/evals/harness.py +949 -0
- aria_code/evals/review_probe.py +132 -0
- aria_code/evals/runner.py +425 -0
- aria_code/evals/scorecard.py +322 -0
- aria_code/evals/summary.py +125 -0
- aria_code/external_agent_runner.py +115 -0
- aria_code/figma_client.py +4 -0
- aria_code/file_analysis_tools.py +4 -0
- aria_code/finance_formulas.py +4 -0
- aria_code/football_data_client.py +4 -0
- aria_code/image_gen_tools.py +4 -0
- aria_code/intent_classifier.py +459 -0
- aria_code/kling_video_client.py +4 -0
- aria_code/licensing.py +151 -0
- aria_code/local_finance_tools.py +4 -0
- aria_code/local_image_provider.py +4 -0
- aria_code/local_llm_provider.py +4 -0
- aria_code/macro_tools.py +4 -0
- aria_code/markdown_pdf.py +289 -0
- aria_code/market_data_client.py +4 -0
- aria_code/mcp_client.py +744 -0
- aria_code/memory_manager.py +273 -0
- aria_code/model_capability.py +512 -0
- aria_code/notification_tools.py +4 -0
- aria_code/openai_image_client.py +4 -0
- aria_code/packages/__init__.py +26 -0
- aria_code/packages/adk_bridge/__init__.py +11 -0
- aria_code/packages/adk_bridge/code_review_tools.py +49 -0
- aria_code/packages/adk_bridge/market_tools.py +125 -0
- aria_code/packages/aria_agents/__init__.py +5 -0
- aria_code/packages/aria_agents/manifest.py +78 -0
- aria_code/packages/aria_core/__init__.py +34 -0
- aria_code/packages/aria_core/architecture.py +269 -0
- aria_code/packages/aria_core/export.py +147 -0
- aria_code/packages/aria_core/manifest.py +65 -0
- aria_code/packages/aria_core/paths.py +57 -0
- aria_code/packages/aria_core/secure_file.py +68 -0
- aria_code/packages/aria_infra/__init__.py +15 -0
- aria_code/packages/aria_infra/arthera.py +58 -0
- aria_code/packages/aria_infra/doctor.py +246 -0
- aria_code/packages/aria_infra/product.py +40 -0
- aria_code/packages/aria_mcp/__init__.py +25 -0
- aria_code/packages/aria_mcp/bridge.py +232 -0
- aria_code/packages/aria_mcp/circuit.py +82 -0
- aria_code/packages/aria_mcp/config.py +162 -0
- aria_code/packages/aria_mcp/installer.py +95 -0
- aria_code/packages/aria_mcp/server.py +1705 -0
- aria_code/packages/aria_mcp/tools.py +81 -0
- aria_code/packages/aria_sdk/__init__.py +19 -0
- aria_code/packages/aria_sdk/client.py +393 -0
- aria_code/packages/aria_sdk/providers.py +70 -0
- aria_code/packages/aria_sdk/streaming.py +75 -0
- aria_code/packages/aria_sdk/types.py +86 -0
- aria_code/packages/aria_services/__init__.py +92 -0
- aria_code/packages/aria_services/cache.py +60 -0
- aria_code/packages/aria_services/context.py +356 -0
- aria_code/packages/aria_services/context_checkpoints.py +157 -0
- aria_code/packages/aria_services/data.py +11 -0
- aria_code/packages/aria_services/provider_health.py +304 -0
- aria_code/packages/aria_services/references.py +247 -0
- aria_code/packages/aria_services/registry.py +249 -0
- aria_code/packages/aria_services/research_protocol.py +137 -0
- aria_code/packages/aria_services/research_quality.py +186 -0
- aria_code/packages/aria_services/settings.py +160 -0
- aria_code/packages/aria_services/usage.py +151 -0
- aria_code/packages/aria_skills/__init__.py +46 -0
- aria_code/packages/aria_skills/catalog.py +149 -0
- aria_code/packages/aria_skills/loader.py +455 -0
- aria_code/packages/aria_skills/registry.py +84 -0
- aria_code/packages/aria_tools/__init__.py +5 -0
- aria_code/packages/aria_tools/financial/__init__.py +5 -0
- aria_code/packages/aria_tools/financial/_paths.py +92 -0
- aria_code/packages/aria_tools/registry.py +129 -0
- aria_code/packages/quant_engine/__init__.py +33 -0
- aria_code/packages/quant_engine/_namespace.py +24 -0
- aria_code/packages/quant_engine/backtest/__init__.py +10 -0
- aria_code/packages/quant_engine/backtest/core.py +53 -0
- aria_code/packages/quant_engine/backtest/engine.py +102 -0
- aria_code/packages/quant_engine/portfolio/__init__.py +9 -0
- aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
- aria_code/packages/quant_engine/sports/__init__.py +76 -0
- aria_code/packages/quant_engine/sports/calibrator.py +354 -0
- aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
- aria_code/packages/quant_engine/sports/elo.py +300 -0
- aria_code/packages/quant_engine/sports/form.py +188 -0
- aria_code/packages/quant_engine/sports/h2h.py +195 -0
- aria_code/packages/quant_engine/sports/predictor.py +312 -0
- aria_code/packages/quant_engine/sports/tracker.py +665 -0
- aria_code/packages/quant_engine/stochastic/__init__.py +31 -0
- aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
- aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
- aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- aria_code/packs/__init__.py +85 -0
- aria_code/packs/base.py +168 -0
- aria_code/packs/finance/__init__.py +250 -0
- aria_code/packs/healthcare/__init__.py +40 -0
- aria_code/packs/logistics/__init__.py +171 -0
- aria_code/packs/payments/__init__.py +139 -0
- aria_code/packs/realty/__init__.py +190 -0
- aria_code/packs/registry.py +202 -0
- aria_code/packs/rules.py +90 -0
- aria_code/plan_utils.py +194 -0
- aria_code/plugin_loader.py +328 -0
- aria_code/portfolio_ledger.py +4 -0
- aria_code/preview_server.py +449 -0
- aria_code/privacy/__init__.py +5 -0
- aria_code/privacy/feedback.py +123 -0
- aria_code/project_review.py +278 -0
- aria_code/project_review_api.py +235 -0
- aria_code/project_review_server.py +18 -0
- aria_code/project_tools.py +4 -0
- aria_code/providers/__init__.py +30 -0
- aria_code/providers/google_video_provider.py +256 -0
- aria_code/providers/llm/__init__.py +19 -0
- aria_code/providers/llm/anthropic.py +184 -0
- aria_code/providers/llm/arthera_gateway.py +90 -0
- aria_code/providers/llm/autoconfig.py +213 -0
- aria_code/providers/llm/base.py +139 -0
- aria_code/providers/llm/custom.py +158 -0
- aria_code/providers/llm/ollama.py +137 -0
- aria_code/providers/llm/openai_compat.py +350 -0
- aria_code/providers/llm/registry.py +457 -0
- aria_code/providers/local_image_provider.py +202 -0
- aria_code/providers/local_llm_provider.py +549 -0
- aria_code/providers/ollama_models.py +79 -0
- aria_code/providers/openai_params.py +32 -0
- aria_code/providers/tool_messages.py +100 -0
- aria_code/realty_data_tools.py +4 -0
- aria_code/report_exporters.py +150 -0
- aria_code/report_generator.py +1764 -0
- aria_code/review_service.py +497 -0
- aria_code/runtime/__init__.py +176 -0
- aria_code/runtime/acceptance.py +632 -0
- aria_code/runtime/agent_loop.py +1851 -0
- aria_code/runtime/agent_profiles.py +75 -0
- aria_code/runtime/approval.py +71 -0
- aria_code/runtime/baseline.py +219 -0
- aria_code/runtime/budget.py +232 -0
- aria_code/runtime/checkpoints.py +481 -0
- aria_code/runtime/consult.py +65 -0
- aria_code/runtime/contract.py +282 -0
- aria_code/runtime/delivery.py +432 -0
- aria_code/runtime/events.py +113 -0
- aria_code/runtime/gateway.py +167 -0
- aria_code/runtime/lsp.py +484 -0
- aria_code/runtime/native_index.py +153 -0
- aria_code/runtime/orchestrator.py +55 -0
- aria_code/runtime/processes.py +343 -0
- aria_code/runtime/project_graph.py +614 -0
- aria_code/runtime/repo_map.py +712 -0
- aria_code/runtime/research_bundle.py +105 -0
- aria_code/runtime/review.py +269 -0
- aria_code/runtime/run_state.py +127 -0
- aria_code/runtime/run_store.py +522 -0
- aria_code/runtime/scoped_runs.py +113 -0
- aria_code/runtime/self_healing.py +422 -0
- aria_code/runtime/semantic_diff.py +171 -0
- aria_code/runtime/subagent.py +721 -0
- aria_code/runtime/symbol_edit.py +117 -0
- aria_code/runtime/task_brief.py +46 -0
- aria_code/runtime/task_context.py +145 -0
- aria_code/runtime/task_ledger.py +108 -0
- aria_code/runtime/task_orchestrator.py +90 -0
- aria_code/runtime/task_outcome.py +33 -0
- aria_code/runtime/task_verification.py +266 -0
- aria_code/runtime/task_worktree.py +448 -0
- aria_code/runtime/tool_executor.py +329 -0
- aria_code/runtime/tool_policy.py +107 -0
- aria_code/runtime/transactions.py +216 -0
- aria_code/runtime/windows_job.py +115 -0
- aria_code/runtime/workflows.py +222 -0
- aria_code/runtime/worktrees.py +215 -0
- aria_code/runway_video_client.py +4 -0
- aria_code/safety/__init__.py +27 -0
- aria_code/safety/client_isolation.py +178 -0
- aria_code/safety/permissions.py +339 -0
- aria_code/safety/risk.py +699 -0
- aria_code/safety/sandbox.py +148 -0
- aria_code/safety/service.py +102 -0
- aria_code/service_endpoints.py +14 -0
- aria_code/setup_wizard.py +684 -0
- aria_code/spreadsheet_tools.py +4 -0
- aria_code/strategy_vault.py +4 -0
- aria_code/tools/__init__.py +0 -0
- aria_code/tools/artifact_tools.py +252 -0
- aria_code/tools/broker_tools.py +92 -0
- aria_code/tools/build_quant_engine.py +97 -0
- aria_code/tools/code_audit_tools.py +192 -0
- aria_code/tools/computer_use_tools.py +509 -0
- aria_code/tools/data_analysis_tools.py +831 -0
- aria_code/tools/enterprise_finance_tools.py +202 -0
- aria_code/tools/extended_tools.py +240 -0
- aria_code/tools/file_analysis_tools.py +880 -0
- aria_code/tools/image_gen_tools.py +114 -0
- aria_code/tools/local_finance_tools.py +3488 -0
- aria_code/tools/logistics_carriers.py +11 -0
- aria_code/tools/logistics_inventory.py +11 -0
- aria_code/tools/logistics_io.py +11 -0
- aria_code/tools/logistics_tenancy.py +11 -0
- aria_code/tools/logistics_tools.py +11 -0
- aria_code/tools/macro_tools.py +412 -0
- aria_code/tools/notification_tools.py +249 -0
- aria_code/tools/project_tools.py +525 -0
- aria_code/tools/realty_data_tools.py +662 -0
- aria_code/tools/spreadsheet_tools.py +399 -0
- aria_code/tools/stripe_tools.py +115 -0
- aria_code/tools/video_tools.py +500 -0
- aria_code/training/__init__.py +1 -0
- aria_code/training/compare.py +117 -0
- aria_code/training/prelude_commands.py +111 -0
- aria_code/training/prelude_eval.py +390 -0
- aria_code/training/prelude_real.py +183 -0
- aria_code/training/prelude_train.py +248 -0
- aria_code/training/sft_dataset.py +230 -0
- aria_code/training/trajectory_dataset.py +263 -0
- aria_code/ui/__init__.py +105 -0
- aria_code/ui/banner.py +404 -0
- aria_code/ui/completer.py +571 -0
- aria_code/ui/console.py +285 -0
- aria_code/ui/input_box.py +505 -0
- aria_code/ui/mentions.py +206 -0
- aria_code/ui/picker.py +248 -0
- aria_code/ui/render/__init__.py +11 -0
- aria_code/ui/render/actions.py +297 -0
- aria_code/ui/render/finance.py +1732 -0
- aria_code/ui/render/market.py +253 -0
- aria_code/ui/render/output.py +954 -0
- aria_code/ui/render/responsive.py +75 -0
- aria_code/ui/render/team.py +367 -0
- aria_code/ui/robot.py +239 -0
- aria_code/ui/startup_dashboard.py +110 -0
- aria_code/video_analysis.py +147 -0
- aria_code/video_editor.py +518 -0
- aria_code/video_workflow/__init__.py +8 -0
- aria_code/video_workflow/__main__.py +61 -0
- aria_code/video_workflow/engines/__init__.py +18 -0
- aria_code/video_workflow/engines/base.py +69 -0
- aria_code/video_workflow/engines/custom_model.py +204 -0
- aria_code/video_workflow/engines/google_veo.py +94 -0
- aria_code/video_workflow/engines/local_engine.py +71 -0
- aria_code/video_workflow/engines/registry.py +98 -0
- aria_code/video_workflow/google_models.py +278 -0
- aria_code/video_workflow/music/__init__.py +5 -0
- aria_code/video_workflow/music/foley_service.py +132 -0
- aria_code/video_workflow/music/service.py +212 -0
- aria_code/video_workflow/pipeline.py +620 -0
- aria_code/video_workflow/plan.py +202 -0
- aria_code/video_workflow/session.py +297 -0
- aria_code/workspace/__init__.py +6 -0
- aria_code/workspace/files.py +219 -0
- aria_code/workspace/verify.py +167 -0
- aria_code/workspace_changes.py +199 -0
- aria_code/workspace_service.py +196 -0
- aria_code/workspace_tools.py +150 -0
- aria_code-0.167.0.dist-info/METADATA +330 -0
- aria_code-0.167.0.dist-info/RECORD +601 -0
- aria_code-0.167.0.dist-info/WHEEL +5 -0
- aria_code-0.167.0.dist-info/entry_points.txt +5 -0
- aria_code-0.167.0.dist-info/licenses/LICENSE +201 -0
- aria_code-0.167.0.dist-info/licenses/NOTICE +49 -0
- aria_code-0.167.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,954 @@
|
|
|
1
|
+
"""Generic tool-result and error rendering for Aria Code.
|
|
2
|
+
|
|
3
|
+
All functions accept console / has_rich as parameters so they stay
|
|
4
|
+
import-free from aria_cli.py and testable in isolation.
|
|
5
|
+
|
|
6
|
+
Public surface
|
|
7
|
+
--------------
|
|
8
|
+
FINANCE_TOOL_NAMES frozenset of tool names with dedicated renderers
|
|
9
|
+
clean_tool_error_message(e) short user-facing string from any exception
|
|
10
|
+
error_hint(msg, context) actionable recovery suggestion
|
|
11
|
+
print_error(msg, context, *, console, has_rich, rich_box)
|
|
12
|
+
print_tool_result(...)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import difflib
|
|
18
|
+
import pathlib
|
|
19
|
+
import re
|
|
20
|
+
import time
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ── Finance tool name registry ─────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
FINANCE_TOOL_NAMES: frozenset = frozenset({
|
|
26
|
+
"get_market_data", "get_market_history", "get_crypto_data", "get_forex_data",
|
|
27
|
+
"get_commodities_data", "get_futures_data", "calculate_factors",
|
|
28
|
+
"backtest_strategy", "cloud_backtest", "run_portfolio_backtest", "get_risk_metrics",
|
|
29
|
+
"optimize_positions", "get_sector_performance", "get_northbound_flow",
|
|
30
|
+
"screen_ashare", "get_limit_up_pool", "get_market_indices",
|
|
31
|
+
"analyze_news", "get_bonds_data", "get_ai_signal",
|
|
32
|
+
"get_market_insights", "get_predictions",
|
|
33
|
+
"broker_query", "broker_order",
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ── Narrow-terminal Markdown adaptation ──────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
_TABLE_SEPARATOR_CELL = re.compile(r"^:?-{3,}:?$")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _markdown_table_cells(line: str) -> list[str]:
|
|
43
|
+
"""Split one simple GFM table row without leaking surrounding pipes."""
|
|
44
|
+
stripped = line.strip()
|
|
45
|
+
if not stripped.startswith("|"):
|
|
46
|
+
return []
|
|
47
|
+
return [cell.strip() for cell in stripped.strip("|").split("|")]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _is_markdown_table_separator(line: str) -> bool:
|
|
51
|
+
cells = _markdown_table_cells(line)
|
|
52
|
+
return bool(cells) and all(_TABLE_SEPARATOR_CELL.fullmatch(cell) for cell in cells)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Column headers that name no dimension — the value column of a key/value table.
|
|
56
|
+
_GENERIC_TABLE_HEADERS = {"value", "values", "meaning", "数值", "值", "含义", "说明"}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _table_rows_as_lines(headers: list[str], rows: list[list[str]]) -> list[str]:
|
|
60
|
+
"""One line per table row: the first cell as its label, the rest after it.
|
|
61
|
+
|
|
62
|
+
Each cell used to become its own bullet — "• 指标:最新价 / • 数值:USD 332.89"
|
|
63
|
+
— so a ten-row snapshot table took about thirty lines in an 80-column
|
|
64
|
+
terminal. Generic value columns ("Value", "数值") lose their header; a
|
|
65
|
+
column that names a dimension keeps it ("观察:价格高于 MA20"). A value
|
|
66
|
+
already contained in the one before it ("51.9,中性" then "中性") is dropped.
|
|
67
|
+
"""
|
|
68
|
+
lines: list[str] = []
|
|
69
|
+
for row in rows:
|
|
70
|
+
cells = [(header, (row[pos] if pos < len(row) else "").strip())
|
|
71
|
+
for pos, header in enumerate(headers) if header]
|
|
72
|
+
if not cells:
|
|
73
|
+
continue
|
|
74
|
+
label = cells[0][1] or "—"
|
|
75
|
+
parts: list[str] = []
|
|
76
|
+
previous = ""
|
|
77
|
+
for header, value in cells[1:]:
|
|
78
|
+
if not value or value in ("—", "-") or (previous and value in previous):
|
|
79
|
+
continue
|
|
80
|
+
generic = header.strip().lower() in _GENERIC_TABLE_HEADERS
|
|
81
|
+
parts.append(value if generic else f"{header}{':' if _has_cjk(header) else ': '}{value}")
|
|
82
|
+
previous = value
|
|
83
|
+
colon = ":" if _has_cjk(label) or any(_has_cjk(h) for h, _ in cells) else ": "
|
|
84
|
+
lines.append(f"- **{label}**{colon}{' · '.join(parts) or '—'}")
|
|
85
|
+
return lines
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _has_cjk(text: str) -> bool:
|
|
89
|
+
return any("\u4e00" <= ch <= "\u9fff" for ch in text or "")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def adapt_markdown_for_width(markup: str, width: int, *, table_breakpoint: int = 96) -> str:
|
|
93
|
+
"""Convert GFM tables to stacked records when the terminal is narrow.
|
|
94
|
+
|
|
95
|
+
Rich correctly renders tables, but a three-column research table inside an
|
|
96
|
+
80-column terminal has too little room for Chinese prose. Cells are then
|
|
97
|
+
truncated or wrapped until row relationships become unreadable. The source
|
|
98
|
+
Markdown remains unchanged in saved reports; only the terminal presentation
|
|
99
|
+
is adapted.
|
|
100
|
+
"""
|
|
101
|
+
if width >= table_breakpoint or "|" not in (markup or ""):
|
|
102
|
+
return markup
|
|
103
|
+
|
|
104
|
+
lines = (markup or "").splitlines()
|
|
105
|
+
output: list[str] = []
|
|
106
|
+
index = 0
|
|
107
|
+
in_fence = False
|
|
108
|
+
while index < len(lines):
|
|
109
|
+
line = lines[index]
|
|
110
|
+
if line.lstrip().startswith("```"):
|
|
111
|
+
in_fence = not in_fence
|
|
112
|
+
output.append(line)
|
|
113
|
+
index += 1
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
if (
|
|
117
|
+
not in_fence
|
|
118
|
+
and index + 1 < len(lines)
|
|
119
|
+
and line.strip().startswith("|")
|
|
120
|
+
and _is_markdown_table_separator(lines[index + 1])
|
|
121
|
+
):
|
|
122
|
+
headers = _markdown_table_cells(line)
|
|
123
|
+
cursor = index + 2
|
|
124
|
+
rows: list[list[str]] = []
|
|
125
|
+
while cursor < len(lines) and lines[cursor].strip().startswith("|"):
|
|
126
|
+
row = _markdown_table_cells(lines[cursor])
|
|
127
|
+
if row:
|
|
128
|
+
rows.append(row)
|
|
129
|
+
cursor += 1
|
|
130
|
+
|
|
131
|
+
if headers and rows:
|
|
132
|
+
output.extend(_table_rows_as_lines(headers, rows))
|
|
133
|
+
index = cursor
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
output.append(line)
|
|
137
|
+
index += 1
|
|
138
|
+
return "\n".join(output)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ── Tool display helpers ──────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
def tool_display_kind(tool_name: str) -> str:
|
|
144
|
+
"""Return a user-facing service/tool kind without exposing local targets."""
|
|
145
|
+
if tool_name.startswith("mcp__"):
|
|
146
|
+
return "MCP"
|
|
147
|
+
if tool_name in FINANCE_TOOL_NAMES:
|
|
148
|
+
return "finance tool"
|
|
149
|
+
if tool_name in {"web_search", "search_web"}:
|
|
150
|
+
return "web search"
|
|
151
|
+
if tool_name == "web_fetch":
|
|
152
|
+
return "web fetch"
|
|
153
|
+
if tool_name in {"read_file", "write_file", "edit_file", "list_files", "search_code"}:
|
|
154
|
+
return "file tool"
|
|
155
|
+
if tool_name == "run_command":
|
|
156
|
+
return "shell tool"
|
|
157
|
+
if tool_name.startswith("skill") or tool_name in {"TaskCreate", "TaskUpdate"}:
|
|
158
|
+
return "skill"
|
|
159
|
+
if tool_name.startswith("broker_") or tool_name in {"broker_query", "broker_order"}:
|
|
160
|
+
return "broker tool"
|
|
161
|
+
return "tool"
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def tool_display_label(tool_name: str) -> str:
|
|
165
|
+
"""Short label for activity UI: tool name plus its service kind."""
|
|
166
|
+
if tool_name.startswith("mcp__"):
|
|
167
|
+
parts = tool_name.split("__")
|
|
168
|
+
if len(parts) >= 3:
|
|
169
|
+
return f"{parts[1]} · {parts[2].replace('_', ' ')} · MCP"
|
|
170
|
+
return "MCP"
|
|
171
|
+
return f"{tool_name} · {tool_display_kind(tool_name)}"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
_RISK_STYLE = {0: "green", 1: "green", 2: "yellow", 3: "bold yellow", 4: "bold red"}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def format_risk_card(assessment, *, impact: str = "") -> list[tuple[str, str]]:
|
|
178
|
+
"""What an action touches, as (style, line) pairs, shown above its approval.
|
|
179
|
+
|
|
180
|
+
The prompt used to ask about a command; this says what the command does:
|
|
181
|
+
its level and score, whether it can be undone, the hosts, files and
|
|
182
|
+
systems it reaches, and why it is rated that way.
|
|
183
|
+
"""
|
|
184
|
+
level = assessment.level
|
|
185
|
+
head = (f"Risk L{level} {assessment.name} · {assessment.score}/100 · "
|
|
186
|
+
f"{'reversible' if assessment.reversible else 'not reversible'}")
|
|
187
|
+
lines = [(_RISK_STYLE.get(level, ""), head)]
|
|
188
|
+
|
|
189
|
+
def _row(label: str, values) -> None:
|
|
190
|
+
values = [str(v) for v in values if str(v)]
|
|
191
|
+
if values:
|
|
192
|
+
shown = ", ".join(values[:4]) + (f" +{len(values) - 4}" if len(values) > 4 else "")
|
|
193
|
+
lines.append(("dim", f"{label:<9}{shown}"))
|
|
194
|
+
|
|
195
|
+
_row("Network", assessment.hosts)
|
|
196
|
+
_row("Files", assessment.files)
|
|
197
|
+
_row("Impact", [impact] if impact else [])
|
|
198
|
+
_row("Affects", assessment.systems)
|
|
199
|
+
_row("Why", assessment.reasons[:2])
|
|
200
|
+
return lines
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _file_impact(assessment) -> str:
|
|
204
|
+
"""What depends on the files an edit touches, from the cached project graph."""
|
|
205
|
+
files = [str(f) for f in (getattr(assessment, "files", None) or ()) if str(f).startswith("/")]
|
|
206
|
+
if not files:
|
|
207
|
+
return ""
|
|
208
|
+
try:
|
|
209
|
+
from aria_code.runtime.project_graph import impact_line, impact_of_paths
|
|
210
|
+
|
|
211
|
+
return impact_line(impact_of_paths(files))
|
|
212
|
+
except Exception:
|
|
213
|
+
return ""
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def print_risk_card(console, assessment) -> None:
|
|
217
|
+
from rich.markup import escape
|
|
218
|
+
|
|
219
|
+
for style, line in format_risk_card(assessment, impact=_file_impact(assessment)):
|
|
220
|
+
if console is None:
|
|
221
|
+
print(f" {line}")
|
|
222
|
+
else:
|
|
223
|
+
text = f" {escape(line)}"
|
|
224
|
+
console.print(f"[{style}]{text}[/{style}]" if style else text, highlight=False)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
_DELIVERY_HEADINGS = {"Changed", "Verified", "Behaviour", "Review", "Risk", "Contract", "Checkpoint", "Next"}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def format_delivery_report(delivery: dict, *, run_id: str = "", root=None) -> list[tuple[str, str]]:
|
|
231
|
+
"""The runtime's delivery report as (style, line) pairs, for any console.
|
|
232
|
+
|
|
233
|
+
The text comes from :meth:`DeliveryReport.render`; this only colours it:
|
|
234
|
+
the status by outcome, headings dim, ✓ green, ✗ red, × and ⚠ yellow.
|
|
235
|
+
"""
|
|
236
|
+
from aria_code.runtime.delivery import DeliveryReport
|
|
237
|
+
|
|
238
|
+
report = DeliveryReport.from_dict(delivery)
|
|
239
|
+
hint = f"/rewind code {run_id}" if run_id and report.checkpoints else ""
|
|
240
|
+
styled: list[tuple[str, str]] = []
|
|
241
|
+
for index, line in enumerate(report.render(rewind_hint=hint, root=root).splitlines()):
|
|
242
|
+
stripped = line.strip()
|
|
243
|
+
if index == 0:
|
|
244
|
+
style = "bold green" if report.status == "done" else "bold yellow"
|
|
245
|
+
elif stripped in _DELIVERY_HEADINGS:
|
|
246
|
+
style = "dim"
|
|
247
|
+
elif stripped.startswith("✓"):
|
|
248
|
+
style = "green"
|
|
249
|
+
elif stripped.startswith("✗"):
|
|
250
|
+
style = "red"
|
|
251
|
+
elif stripped.startswith(("×", "⚠")):
|
|
252
|
+
style = "yellow"
|
|
253
|
+
else:
|
|
254
|
+
style = ""
|
|
255
|
+
styled.append((style, line))
|
|
256
|
+
return styled
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def print_delivery_report(console, delivery: dict, *, run_id: str = "", root=None) -> None:
|
|
260
|
+
"""Print the report indented under the answer; plain print without Rich."""
|
|
261
|
+
from rich.markup import escape
|
|
262
|
+
|
|
263
|
+
lines = format_delivery_report(delivery, run_id=run_id, root=root)
|
|
264
|
+
if console is None:
|
|
265
|
+
print()
|
|
266
|
+
for _, line in lines:
|
|
267
|
+
print(f" {line}" if line else "")
|
|
268
|
+
return
|
|
269
|
+
console.print()
|
|
270
|
+
for style, line in lines:
|
|
271
|
+
text = f" {escape(line)}" if line else ""
|
|
272
|
+
console.print(f"[{style}]{text}[/{style}]" if style and text else text, highlight=False)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def format_turn_footer(metadata, *, mode: str = "compact", copy_available: bool = False) -> str:
|
|
276
|
+
"""Return the post-response status line.
|
|
277
|
+
|
|
278
|
+
``full`` keeps the historical token-heavy line for debugging. ``compact``
|
|
279
|
+
is the default interactive UI: elapsed time, provider, tools, and /copy.
|
|
280
|
+
"""
|
|
281
|
+
mode = (mode or "compact").strip().lower()
|
|
282
|
+
if mode in {"off", "none", "false", "0"}:
|
|
283
|
+
return ""
|
|
284
|
+
parts = list(getattr(metadata, "parts", []) or [])
|
|
285
|
+
if mode in {"full", "debug", "verbose"}:
|
|
286
|
+
footer = " · ".join(parts)
|
|
287
|
+
if copy_available:
|
|
288
|
+
footer = f"{footer} /copy" if footer else "/copy"
|
|
289
|
+
return footer
|
|
290
|
+
|
|
291
|
+
elapsed = parts[0] if parts else ""
|
|
292
|
+
provider = str(getattr(metadata, "provider", "") or "").strip()
|
|
293
|
+
tools = list(getattr(metadata, "tools", []) or [])
|
|
294
|
+
out: list[str] = []
|
|
295
|
+
if elapsed:
|
|
296
|
+
out.append(elapsed)
|
|
297
|
+
if provider and provider not in {"aws", "local"}:
|
|
298
|
+
out.append(provider)
|
|
299
|
+
if tools:
|
|
300
|
+
shown = " ".join(str(t) for t in tools[:3])
|
|
301
|
+
if len(tools) > 3:
|
|
302
|
+
shown += f" +{len(tools) - 3}"
|
|
303
|
+
out.append(shown)
|
|
304
|
+
if copy_available:
|
|
305
|
+
out.append("/copy")
|
|
306
|
+
return " · ".join(out)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def file_uri(path: object) -> str:
|
|
310
|
+
"""A terminal-hyperlink target for a local file: file:///Users/…/chart.html.
|
|
311
|
+
|
|
312
|
+
Saved files were linked as [link=/Users/…/chart.html]: OSC 8 hyperlinks
|
|
313
|
+
take a URI, so terminals that support them had nothing to open. A value
|
|
314
|
+
that already has a scheme (https://…) is returned unchanged.
|
|
315
|
+
"""
|
|
316
|
+
text = str(path or "")
|
|
317
|
+
if not text or "://" in text:
|
|
318
|
+
return text
|
|
319
|
+
try:
|
|
320
|
+
return pathlib.Path(text).expanduser().resolve().as_uri()
|
|
321
|
+
except (OSError, ValueError):
|
|
322
|
+
return text
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def display_path(path: object, *, fallback: str = "file") -> str:
|
|
326
|
+
"""Return a path-safe display value for user-facing UI."""
|
|
327
|
+
if not path:
|
|
328
|
+
return fallback
|
|
329
|
+
try:
|
|
330
|
+
name = pathlib.Path(str(path)).name
|
|
331
|
+
except Exception:
|
|
332
|
+
name = ""
|
|
333
|
+
return name or fallback
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
# ── Error helpers ──────────────────────────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
def clean_tool_error_message(error: object) -> str:
|
|
339
|
+
raw = str(error or "failed").strip()
|
|
340
|
+
low = raw.lower()
|
|
341
|
+
if not raw:
|
|
342
|
+
return "操作失败"
|
|
343
|
+
if "curl: (28)" in low or "timed out" in low or "timeout" in low:
|
|
344
|
+
return "请求超时,数据源暂时不可用。请稍后重试或运行 /health 检查服务。"
|
|
345
|
+
if "connection refused" in low:
|
|
346
|
+
return "连接被拒绝,服务暂时不可用。请检查本地服务或网络。"
|
|
347
|
+
if "connection aborted" in low or "remotedisconnected" in low:
|
|
348
|
+
return "网络连接中断,数据源未完成响应。请稍后重试。"
|
|
349
|
+
# Generic connection / proxy / DNS failures — collapse the verbose
|
|
350
|
+
# urllib3 HTTPSConnectionPool(...) dump into a single readable line.
|
|
351
|
+
if any(s in low for s in (
|
|
352
|
+
"httpsconnectionpool", "httpconnectionpool", "max retries exceeded",
|
|
353
|
+
"proxyerror", "failed to establish a new connection",
|
|
354
|
+
"nameresolutionerror", "getaddrinfo failed", "newconnectionerror",
|
|
355
|
+
)):
|
|
356
|
+
import re as _re3
|
|
357
|
+
_host = _re3.search(r"host=['\"]([^'\"]+)['\"]", raw)
|
|
358
|
+
_hint = f"(数据源 {_host.group(1)})" if _host else ""
|
|
359
|
+
return f"数据源连接失败{_hint},可能是网络或代理问题。请检查网络后重试。"
|
|
360
|
+
if "rate" in low or "429" in low or "too many requests" in low:
|
|
361
|
+
return "数据源请求频率受限,请稍后重试。"
|
|
362
|
+
# Collapse verbose HTTP error strings: "web_fetch failed: 401 Client Error: Unauthorized for url: https://..."
|
|
363
|
+
import re as _re
|
|
364
|
+
_http = _re.match(r"web_fetch failed:\s*(\d{3})\s+\w[\w\s]+?:\s*([\w\s]+?)(?:\s+for url:.*)?$", raw, _re.I)
|
|
365
|
+
if _http:
|
|
366
|
+
code, phrase = _http.group(1), _http.group(2).strip()
|
|
367
|
+
return f"HTTP {code} {phrase}"
|
|
368
|
+
if "traceback" in low:
|
|
369
|
+
return raw.splitlines()[-1][:160] if raw.splitlines() else "运行失败"
|
|
370
|
+
return raw[:200]
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def error_hint(error: str, context: str = "") -> str:
|
|
374
|
+
err_lower = error.lower() if error else ""
|
|
375
|
+
if "connection" in err_lower or "refused" in err_lower or "unreachable" in err_lower:
|
|
376
|
+
return "Hint: Backend unreachable. Try /health or check your network."
|
|
377
|
+
if "timeout" in err_lower or "timed out" in err_lower:
|
|
378
|
+
return "Hint: Request timed out. Try again or check /health."
|
|
379
|
+
# External web pages that block scraping (paywall / anti-bot) — NOT an Aria
|
|
380
|
+
# login problem, so /login must not be suggested.
|
|
381
|
+
_is_web = any(m in err_lower for m in (
|
|
382
|
+
"http://", "https://", "www.", ".com", ".org", ".net",
|
|
383
|
+
"web_fetch", "web fetch", "forbidden",
|
|
384
|
+
))
|
|
385
|
+
def code(n: str) -> bool:
|
|
386
|
+
# A status code as a number of its own: "1500 bars" is not a 500.
|
|
387
|
+
return re.search(rf"(?<!\d){n}(?!\d)", err_lower) is not None
|
|
388
|
+
|
|
389
|
+
if code("401") or "unauthorized" in err_lower:
|
|
390
|
+
if any(h in err_lower for h in ("finnhub", "alphavantage", "polygon", "api/v1", "api/v2/finance")):
|
|
391
|
+
return "Hint: API key required — /apikey set finnhub <KEY> (free at finnhub.io)"
|
|
392
|
+
if _is_web:
|
|
393
|
+
return "Hint: This site blocks automated access (paywall/anti-bot). Try another source."
|
|
394
|
+
return "Hint: Authentication required. Run /login to sign in."
|
|
395
|
+
if code("403") or "forbidden" in err_lower:
|
|
396
|
+
if _is_web:
|
|
397
|
+
return "Hint: This site blocks automated access (paywall/anti-bot). Try another source."
|
|
398
|
+
return "Hint: Access denied. Check your API key or subscription."
|
|
399
|
+
# "rate" alone matched strategy, generate and separate: "Unknown strategy"
|
|
400
|
+
# came with "Rate limited. Wait a moment and try again."
|
|
401
|
+
if code("429") or re.search(r"rate[\s_-]?limit|too many requests", err_lower):
|
|
402
|
+
return "Hint: Rate limited. Wait a moment and try again."
|
|
403
|
+
if ("ollama" in err_lower or "ollama http" in err_lower) and (
|
|
404
|
+
"not found" in err_lower or "404" in err_lower
|
|
405
|
+
):
|
|
406
|
+
m = re.search(r"model ['\"]?([^'\"]+)['\"]? not found", err_lower)
|
|
407
|
+
model_hint = m.group(1) if m else "the requested model"
|
|
408
|
+
try:
|
|
409
|
+
from local_llm_provider import list_ollama_models
|
|
410
|
+
available = list_ollama_models("http://localhost:11434")
|
|
411
|
+
if available:
|
|
412
|
+
suggestion = available[0]
|
|
413
|
+
return (
|
|
414
|
+
f"Hint: Ollama model '{model_hint}' not found.\n"
|
|
415
|
+
f" Available: {', '.join(available[:4])}\n"
|
|
416
|
+
f" Run: /config model {suggestion}"
|
|
417
|
+
)
|
|
418
|
+
except Exception:
|
|
419
|
+
pass
|
|
420
|
+
return (
|
|
421
|
+
"Hint: Ollama model not found. Run `ollama list` to see available models.\n"
|
|
422
|
+
" Or pull one: ollama pull qwen2.5-coder:7b"
|
|
423
|
+
)
|
|
424
|
+
# "File not found" is a path error. Tell the model firmly NOT to keep
|
|
425
|
+
# guessing filenames (it otherwise loops app.py→script.py→main.py…).
|
|
426
|
+
if "file not found" in err_lower or "no such file" in err_lower:
|
|
427
|
+
return ("Hint: This file does not exist. Do NOT guess other filenames — "
|
|
428
|
+
"list the directory first, or this question may not need a file at all.")
|
|
429
|
+
if code("404") and context == "tool":
|
|
430
|
+
return "Hint: Tool not available. Check /tools for available tools."
|
|
431
|
+
if "not found" in err_lower and context == "session":
|
|
432
|
+
return "Hint: Session not found. Run /sessions to list available."
|
|
433
|
+
if code("404") or ("not found" in err_lower and context not in ("tool", "")):
|
|
434
|
+
return "Hint: Resource not found. Check the symbol or path."
|
|
435
|
+
if "no data" in err_lower or "no result" in err_lower:
|
|
436
|
+
return "Hint: No data returned. Verify the symbol spelling."
|
|
437
|
+
if code("500") or "internal server" in err_lower:
|
|
438
|
+
return "Hint: Server error. Try again in a moment or /health to check."
|
|
439
|
+
if context == "login":
|
|
440
|
+
return "Hint: Check email/password. Usage: /login email password"
|
|
441
|
+
return ""
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
# ── Error panel & tree formatting ──────────────────────────────────────────────
|
|
445
|
+
|
|
446
|
+
def print_hanging(console, prefix: str, text: str, style: str = "") -> None:
|
|
447
|
+
"""One message after a fixed prefix, wrapped lines aligned under its first word.
|
|
448
|
+
|
|
449
|
+
The text is plain, never markup: an error such as "/init [--force]" lost
|
|
450
|
+
its brackets to Rich, and a long one wrapped back to column 0 under the
|
|
451
|
+
"└" instead of beside it.
|
|
452
|
+
"""
|
|
453
|
+
from rich.table import Table
|
|
454
|
+
from rich.text import Text
|
|
455
|
+
|
|
456
|
+
grid = Table.grid(padding=0)
|
|
457
|
+
grid.add_column(no_wrap=True)
|
|
458
|
+
grid.add_column(overflow="fold")
|
|
459
|
+
grid.add_row(Text(prefix, style=style), Text(text, style=style))
|
|
460
|
+
console.print(grid)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
# The contexts error_hint() understands; anything else given is a hint to show.
|
|
464
|
+
ERROR_CONTEXTS = frozenset({"", "tool", "login", "session", "screenshot", "vision", "browser",
|
|
465
|
+
"browser screenshot", "deep"})
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def print_error(
|
|
469
|
+
msg: str,
|
|
470
|
+
context: str = "",
|
|
471
|
+
*,
|
|
472
|
+
console,
|
|
473
|
+
has_rich: bool,
|
|
474
|
+
rich_box=None,
|
|
475
|
+
use_panel: bool = False,
|
|
476
|
+
) -> None:
|
|
477
|
+
# The second argument is either where the error came from ("tool",
|
|
478
|
+
# "login"), from which a hint is guessed, or the hint itself. Most callers
|
|
479
|
+
# pass the hint: "请先用 /broker add 添加", "Usage: /compare SYMBOL …",
|
|
480
|
+
# "Install: pip install mss pillow". It was taken for a context and never
|
|
481
|
+
# shown, and a guess took its place.
|
|
482
|
+
if str(context or "").strip().lower() in ERROR_CONTEXTS:
|
|
483
|
+
hint = error_hint(msg, context)
|
|
484
|
+
else:
|
|
485
|
+
hint = str(context)
|
|
486
|
+
if not use_panel:
|
|
487
|
+
# Claude Code style: clean └ tree connector for inline error guidance
|
|
488
|
+
if has_rich:
|
|
489
|
+
print_hanging(console, " └ ", msg, "red")
|
|
490
|
+
if hint:
|
|
491
|
+
for hline in hint.splitlines():
|
|
492
|
+
print_hanging(console, " ", hline, "dim")
|
|
493
|
+
else:
|
|
494
|
+
print(f" └ {msg}")
|
|
495
|
+
if hint:
|
|
496
|
+
for hline in hint.splitlines():
|
|
497
|
+
print(f" {hline}")
|
|
498
|
+
return
|
|
499
|
+
|
|
500
|
+
if has_rich:
|
|
501
|
+
from rich.panel import Panel
|
|
502
|
+
box_style = getattr(rich_box, "ROUNDED", None) if rich_box else None
|
|
503
|
+
body = f"[red]{msg}[/red]"
|
|
504
|
+
if hint:
|
|
505
|
+
body += f"\n[dim]{hint}[/dim]"
|
|
506
|
+
console.print(Panel(body, border_style="red", box=box_style, padding=(0, 1)))
|
|
507
|
+
else:
|
|
508
|
+
print(msg)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
# ── Tool result ────────────────────────────────────────────────────────────────
|
|
512
|
+
|
|
513
|
+
def print_tool_result(
|
|
514
|
+
tool_name: str,
|
|
515
|
+
result: dict,
|
|
516
|
+
elapsed: float = 0,
|
|
517
|
+
params: dict = None,
|
|
518
|
+
*,
|
|
519
|
+
console,
|
|
520
|
+
has_rich: bool,
|
|
521
|
+
rich_box,
|
|
522
|
+
print_finance_fn, # callable(tool_name, result) for finance tools
|
|
523
|
+
bot_mode: bool = False,
|
|
524
|
+
) -> None:
|
|
525
|
+
"""Render a tool result summary — Codex-style ⎿ tree connector."""
|
|
526
|
+
if bot_mode:
|
|
527
|
+
return
|
|
528
|
+
|
|
529
|
+
ts = f" [dim]{elapsed:.1f}s[/dim]" if elapsed >= 0.1 else ""
|
|
530
|
+
ts_plain = f" {elapsed:.1f}s" if elapsed >= 0.1 else ""
|
|
531
|
+
params = params or {}
|
|
532
|
+
|
|
533
|
+
if tool_name in FINANCE_TOOL_NAMES:
|
|
534
|
+
print_finance_fn(tool_name, result)
|
|
535
|
+
if ts and has_rich:
|
|
536
|
+
console.print(f" [dim]⎿[/dim]{ts}")
|
|
537
|
+
return
|
|
538
|
+
|
|
539
|
+
if result.get("success"):
|
|
540
|
+
data = result.get("data", {})
|
|
541
|
+
|
|
542
|
+
if tool_name == "write_file":
|
|
543
|
+
lines = data.get("lines") or (params.get("content", "").count("\n") + 1 if params.get("content") else 0)
|
|
544
|
+
size = data.get("size_bytes") or len((params.get("content", "") or "").encode())
|
|
545
|
+
size_str = f"{size}B" if size < 1024 else f"{size // 1024}KB"
|
|
546
|
+
if has_rich:
|
|
547
|
+
console.print(f" [dim]⎿[/dim] [green]✓[/green] [dim]file tool {lines} lines {size_str}[/dim]{ts}")
|
|
548
|
+
else:
|
|
549
|
+
print(f" ⎿ ✓ file tool {lines} lines {size_str}{ts_plain}")
|
|
550
|
+
|
|
551
|
+
elif tool_name == "edit_file":
|
|
552
|
+
old = params.get("old_string", "")
|
|
553
|
+
new = params.get("new_string", "")
|
|
554
|
+
if old and new and has_rich:
|
|
555
|
+
diff = list(difflib.unified_diff(
|
|
556
|
+
old.splitlines(keepends=True),
|
|
557
|
+
new.splitlines(keepends=True),
|
|
558
|
+
lineterm="",
|
|
559
|
+
))
|
|
560
|
+
if diff:
|
|
561
|
+
_hdr = " [dim]⎿[/dim] [#C08050]file tool[/#C08050]"
|
|
562
|
+
console.print(f"{_hdr}{ts}")
|
|
563
|
+
from rich.syntax import Syntax
|
|
564
|
+
from rich.panel import Panel
|
|
565
|
+
diff_text = "".join(diff[2:])
|
|
566
|
+
console.print(Panel(
|
|
567
|
+
Syntax(diff_text, "diff", theme="monokai", padding=(0, 1)),
|
|
568
|
+
border_style="dim",
|
|
569
|
+
box=rich_box.SIMPLE,
|
|
570
|
+
))
|
|
571
|
+
else:
|
|
572
|
+
console.print(f" [dim]⎿ no change[/dim]{ts}")
|
|
573
|
+
elif has_rich:
|
|
574
|
+
console.print(f" [dim]⎿ edited[/dim]{ts}")
|
|
575
|
+
else:
|
|
576
|
+
print(f" ⎿ edited{ts_plain}")
|
|
577
|
+
|
|
578
|
+
elif tool_name == "run_command":
|
|
579
|
+
stdout = data.get("stdout", "").strip()
|
|
580
|
+
returncode = data.get("returncode", data.get("exit_code", 0))
|
|
581
|
+
if has_rich:
|
|
582
|
+
from rich.panel import Panel
|
|
583
|
+
rc_color = "green" if returncode == 0 else "red"
|
|
584
|
+
rc_icon = "✓" if returncode == 0 else "✗"
|
|
585
|
+
console.print(f" [dim]⎿[/dim] [{rc_color}]{rc_icon} exit {returncode}[/{rc_color}]{ts}")
|
|
586
|
+
if stdout:
|
|
587
|
+
out_lines = stdout.splitlines()
|
|
588
|
+
if len(out_lines) > 15:
|
|
589
|
+
head = out_lines[:5]
|
|
590
|
+
tail = out_lines[-10:]
|
|
591
|
+
truncated = "\n".join(head) + f"\n[dim]… ({len(out_lines) - 15} lines hidden) …[/dim]\n" + "\n".join(tail)
|
|
592
|
+
if data.get("full_output_path"):
|
|
593
|
+
truncated += f"\n[dim]full output saved: {data.get('full_output_path')}[/dim]"
|
|
594
|
+
console.print(Panel(
|
|
595
|
+
f"[dim]{truncated}[/dim]",
|
|
596
|
+
border_style="dim",
|
|
597
|
+
box=rich_box.SIMPLE,
|
|
598
|
+
padding=(0, 1),
|
|
599
|
+
))
|
|
600
|
+
else:
|
|
601
|
+
truncated = "\n".join(out_lines)
|
|
602
|
+
if data.get("full_output_path"):
|
|
603
|
+
truncated += f"\n[dim]full output saved: {data.get('full_output_path')}[/dim]"
|
|
604
|
+
console.print(Panel(
|
|
605
|
+
f"[dim]{truncated}[/dim]",
|
|
606
|
+
border_style="dim",
|
|
607
|
+
box=rich_box.SIMPLE,
|
|
608
|
+
padding=(0, 1),
|
|
609
|
+
))
|
|
610
|
+
else:
|
|
611
|
+
print(f" ⎿ exit {returncode}{ts_plain}")
|
|
612
|
+
for ol in stdout.splitlines()[:4]:
|
|
613
|
+
print(f" {ol[:100]}")
|
|
614
|
+
|
|
615
|
+
elif tool_name == "read_file":
|
|
616
|
+
lines = data.get("lines", 0)
|
|
617
|
+
if has_rich:
|
|
618
|
+
console.print(f" [dim]⎿ file tool {lines} lines[/dim]{ts}")
|
|
619
|
+
else:
|
|
620
|
+
print(f" ⎿ file tool {lines} lines{ts_plain}")
|
|
621
|
+
|
|
622
|
+
elif tool_name == "list_files":
|
|
623
|
+
count = data.get("count", 0)
|
|
624
|
+
if has_rich:
|
|
625
|
+
color = "yellow" if count == 0 else "dim"
|
|
626
|
+
msg = "0 items — no matches" if count == 0 else f"{count} items"
|
|
627
|
+
console.print(f" [{color}]⎿ {msg}[/{color}]{ts}")
|
|
628
|
+
else:
|
|
629
|
+
print(f" ⎿ {count} items{ts_plain}")
|
|
630
|
+
|
|
631
|
+
elif tool_name == "search_code":
|
|
632
|
+
matches = len(data.get("matches", []))
|
|
633
|
+
if has_rich:
|
|
634
|
+
console.print(f" [dim]⎿ {matches} matches[/dim]{ts}")
|
|
635
|
+
else:
|
|
636
|
+
print(f" ⎿ {matches} matches{ts_plain}")
|
|
637
|
+
|
|
638
|
+
elif tool_name == "web_fetch":
|
|
639
|
+
length = data.get("length", 0)
|
|
640
|
+
trunc = data.get("truncated", False)
|
|
641
|
+
len_str = f" {length:,} chars" if length else ""
|
|
642
|
+
trunc_str = " [dim]truncated[/dim]" if trunc else ""
|
|
643
|
+
if has_rich:
|
|
644
|
+
console.print(f" [dim]⎿ web fetch{len_str}[/dim]{trunc_str}{ts}")
|
|
645
|
+
else:
|
|
646
|
+
print(f" ⎿ web fetch{ts_plain}")
|
|
647
|
+
|
|
648
|
+
elif tool_name in ("web_search", "search_web"):
|
|
649
|
+
results = data.get("results", [])
|
|
650
|
+
count = len(results)
|
|
651
|
+
if has_rich:
|
|
652
|
+
console.print(f" [dim]⎿ {count} results[/dim]{ts}")
|
|
653
|
+
else:
|
|
654
|
+
print(f" ⎿ {count} results{ts_plain}")
|
|
655
|
+
|
|
656
|
+
else:
|
|
657
|
+
short = tool_display_kind(tool_name)
|
|
658
|
+
if has_rich:
|
|
659
|
+
console.print(f" [dim]⎿ {short} done[/dim]{ts}")
|
|
660
|
+
else:
|
|
661
|
+
print(f" ⎿ done{ts_plain}")
|
|
662
|
+
|
|
663
|
+
else:
|
|
664
|
+
error = clean_tool_error_message(result.get("error", "failed"))
|
|
665
|
+
hint = error_hint(str(error), context="tool")
|
|
666
|
+
if has_rich:
|
|
667
|
+
console.print(f" [dim]⎿[/dim] [red]✗ {error[:120]}[/red]")
|
|
668
|
+
if hint:
|
|
669
|
+
console.print(f" [dim]{hint}[/dim]")
|
|
670
|
+
else:
|
|
671
|
+
print(f" ⎿ ✗ {error[:80]}")
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
# ── Activity group (OpenClaw-style batch summary) ──────────────────────────────
|
|
675
|
+
|
|
676
|
+
def _one_line_tool_summary(
|
|
677
|
+
tool_name: str,
|
|
678
|
+
result: dict,
|
|
679
|
+
elapsed: float,
|
|
680
|
+
params: dict,
|
|
681
|
+
) -> tuple[str, str]:
|
|
682
|
+
"""Return (status_markup, detail_markup) for one tool in an activity table."""
|
|
683
|
+
params = params or {}
|
|
684
|
+
ts = f"[dim] {elapsed:.1f}s[/dim]" if elapsed >= 0.1 else ""
|
|
685
|
+
|
|
686
|
+
if not result.get("success"):
|
|
687
|
+
error = clean_tool_error_message(result.get("error", "failed"))
|
|
688
|
+
return "[red]✗[/red]", f"[red]{error[:80]}[/red]{ts}"
|
|
689
|
+
|
|
690
|
+
data = result.get("data", {})
|
|
691
|
+
kind = tool_display_kind(tool_name)
|
|
692
|
+
|
|
693
|
+
if tool_name == "write_file":
|
|
694
|
+
lines = data.get("lines") or (params.get("content", "").count("\n") + 1 if params.get("content") else 0)
|
|
695
|
+
size = data.get("size_bytes") or len((params.get("content", "") or "").encode())
|
|
696
|
+
size_str = f"{size}B" if size < 1024 else f"{size // 1024}KB"
|
|
697
|
+
return "[green]✓[/green]", f"[dim]{kind} {lines} lines {size_str}[/dim]{ts}"
|
|
698
|
+
|
|
699
|
+
elif tool_name == "edit_file":
|
|
700
|
+
return "[green]✓[/green]", f"[dim]edited {kind}[/dim]{ts}"
|
|
701
|
+
|
|
702
|
+
elif tool_name == "run_command":
|
|
703
|
+
rc = data.get("returncode", data.get("exit_code", 0))
|
|
704
|
+
icon = "[green]✓[/green]" if rc == 0 else "[red]✗[/red]"
|
|
705
|
+
color = "green" if rc == 0 else "red"
|
|
706
|
+
suffix = " [dim]· full output saved[/dim]" if data.get("full_output_path") else ""
|
|
707
|
+
return icon, f"[{color}]exit {rc}[/{color}]{suffix}{ts}"
|
|
708
|
+
|
|
709
|
+
elif tool_name == "read_file":
|
|
710
|
+
lines = data.get("lines", 0)
|
|
711
|
+
return "[green]✓[/green]", f"[dim]{kind} {lines} lines[/dim]{ts}"
|
|
712
|
+
|
|
713
|
+
elif tool_name == "list_files":
|
|
714
|
+
count = data.get("count", 0)
|
|
715
|
+
color = "yellow" if count == 0 else "dim"
|
|
716
|
+
msg = "no matches" if count == 0 else f"{count} items"
|
|
717
|
+
return "[green]✓[/green]", f"[{color}]{msg}[/{color}]{ts}"
|
|
718
|
+
|
|
719
|
+
elif tool_name == "search_code":
|
|
720
|
+
matches = len(data.get("matches", []))
|
|
721
|
+
return "[green]✓[/green]", f"[dim]{matches} matches[/dim]{ts}"
|
|
722
|
+
|
|
723
|
+
elif tool_name == "web_fetch":
|
|
724
|
+
length = data.get("length", 0)
|
|
725
|
+
len_s = f" {length:,}c" if length else ""
|
|
726
|
+
return "[green]✓[/green]", f"[dim]{kind}{len_s}[/dim]{ts}"
|
|
727
|
+
|
|
728
|
+
elif tool_name in ("web_search", "search_web"):
|
|
729
|
+
count = len(data.get("results", []))
|
|
730
|
+
return "[green]✓[/green]", f"[dim]{count} results[/dim]{ts}"
|
|
731
|
+
|
|
732
|
+
else:
|
|
733
|
+
return "[green]✓[/green]", f"[dim]{kind} done[/dim]{ts}"
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
def print_tool_activity_group(
|
|
737
|
+
results: list, # list of (tool_name, result, elapsed, params)
|
|
738
|
+
*,
|
|
739
|
+
console,
|
|
740
|
+
has_rich: bool,
|
|
741
|
+
rich_box,
|
|
742
|
+
print_finance_fn,
|
|
743
|
+
bot_mode: bool = False,
|
|
744
|
+
) -> None:
|
|
745
|
+
"""Render multiple tool results as a compact Activity block (OpenClaw style).
|
|
746
|
+
|
|
747
|
+
For N >= 2 tools: prints a titled table.
|
|
748
|
+
For N == 1: delegates to print_tool_result (single-line).
|
|
749
|
+
"""
|
|
750
|
+
if bot_mode or not results:
|
|
751
|
+
return
|
|
752
|
+
|
|
753
|
+
if len(results) == 1:
|
|
754
|
+
tool_name, result, elapsed, params = results[0]
|
|
755
|
+
print_tool_result(tool_name, result, elapsed, params,
|
|
756
|
+
console=console, has_rich=has_rich, rich_box=rich_box,
|
|
757
|
+
print_finance_fn=print_finance_fn, bot_mode=bot_mode)
|
|
758
|
+
return
|
|
759
|
+
|
|
760
|
+
total_elapsed = sum(e for _, _, e, _ in results)
|
|
761
|
+
n = len(results)
|
|
762
|
+
|
|
763
|
+
# Finance tools: print with dedicated renderer, then add to activity table
|
|
764
|
+
finance_rows = []
|
|
765
|
+
for tool_name, result, elapsed, params in results:
|
|
766
|
+
if tool_name in FINANCE_TOOL_NAMES:
|
|
767
|
+
print_finance_fn(tool_name, result)
|
|
768
|
+
finance_rows.append(tool_name)
|
|
769
|
+
|
|
770
|
+
if has_rich:
|
|
771
|
+
from rich.table import Table
|
|
772
|
+
ts_total = f" [dim]{total_elapsed:.1f}s[/dim]" if total_elapsed >= 0.1 else ""
|
|
773
|
+
header = f"[dim]Activity · {n} tools[/dim]{ts_total}"
|
|
774
|
+
console.print(f"\n {header}")
|
|
775
|
+
tbl = Table.grid(padding=(0, 2))
|
|
776
|
+
tbl.add_column(no_wrap=True, min_width=14, style="dim") # tool name
|
|
777
|
+
tbl.add_column(no_wrap=True, min_width=2) # status icon
|
|
778
|
+
tbl.add_column() # detail
|
|
779
|
+
|
|
780
|
+
from collections import OrderedDict
|
|
781
|
+
_mcp_groups: "OrderedDict[str, list]" = OrderedDict()
|
|
782
|
+
for tool_name, result, elapsed, params in results:
|
|
783
|
+
if tool_name in finance_rows:
|
|
784
|
+
icon = "[green]✓[/green]" if result.get("success") else "[red]✗[/red]"
|
|
785
|
+
tbl.add_row(tool_name, icon, "")
|
|
786
|
+
elif tool_name.startswith("mcp__"):
|
|
787
|
+
# Defer MCP calls — collapse per server below
|
|
788
|
+
_server = tool_name.split("__")[1] if len(tool_name.split("__")) >= 2 else "mcp"
|
|
789
|
+
_mcp_groups.setdefault(_server, []).append((tool_name, result))
|
|
790
|
+
else:
|
|
791
|
+
icon, detail = _one_line_tool_summary(tool_name, result, elapsed, params)
|
|
792
|
+
tbl.add_row(f"[dim]{tool_name}[/dim]", icon, detail)
|
|
793
|
+
|
|
794
|
+
# Collapsed MCP rows: "server · tool" for one, "called N times" for many
|
|
795
|
+
for _server, _calls in _mcp_groups.items():
|
|
796
|
+
_all_ok = all(r.get("success") for _, r in _calls)
|
|
797
|
+
_icon = "[green]✓[/green]" if _all_ok else "[red]✗[/red]"
|
|
798
|
+
if len(_calls) == 1:
|
|
799
|
+
_tn = _calls[0][0].split("__")
|
|
800
|
+
_label = _tn[2].replace("_", " ") if len(_tn) >= 3 else _server
|
|
801
|
+
tbl.add_row(f"[dim]{_server}[/dim]", _icon, f"[dim]{_label} · MCP[/dim]")
|
|
802
|
+
else:
|
|
803
|
+
tbl.add_row(f"[dim]{_server}[/dim]", _icon,
|
|
804
|
+
f"[dim]called {len(_calls)} times · MCP[/dim]")
|
|
805
|
+
|
|
806
|
+
from rich.padding import Padding
|
|
807
|
+
console.print(Padding(tbl, (0, 0, 0, 4)))
|
|
808
|
+
|
|
809
|
+
# For run_command with stdout, still print the output panel
|
|
810
|
+
for tool_name, result, elapsed, params in results:
|
|
811
|
+
if tool_name == "run_command" and result.get("success"):
|
|
812
|
+
stdout = result.get("data", {}).get("stdout", "").strip()
|
|
813
|
+
if stdout:
|
|
814
|
+
from rich.panel import Panel
|
|
815
|
+
out_lines = stdout.splitlines()
|
|
816
|
+
if len(out_lines) > 15:
|
|
817
|
+
head = out_lines[:5]
|
|
818
|
+
tail = out_lines[-10:]
|
|
819
|
+
truncated = "\n".join(head) + f"\n[dim]… ({len(out_lines) - 15} lines hidden) …[/dim]\n" + "\n".join(tail)
|
|
820
|
+
if result.get("data", {}).get("full_output_path"):
|
|
821
|
+
truncated += f"\n[dim]full output saved: {result.get('data', {}).get('full_output_path')}[/dim]"
|
|
822
|
+
console.print(Panel(f"[dim]{truncated}[/dim]",
|
|
823
|
+
border_style="dim", box=rich_box.SIMPLE,
|
|
824
|
+
padding=(0, 1)))
|
|
825
|
+
else:
|
|
826
|
+
truncated = "\n".join(out_lines)
|
|
827
|
+
if result.get("data", {}).get("full_output_path"):
|
|
828
|
+
truncated += f"\n[dim]full output saved: {result.get('data', {}).get('full_output_path')}[/dim]"
|
|
829
|
+
console.print(Panel(f"[dim]{truncated}[/dim]",
|
|
830
|
+
border_style="dim", box=rich_box.SIMPLE,
|
|
831
|
+
padding=(0, 1)))
|
|
832
|
+
|
|
833
|
+
# For edit_file, still print diff
|
|
834
|
+
elif tool_name == "edit_file" and result.get("success"):
|
|
835
|
+
old = (params or {}).get("old_string", "")
|
|
836
|
+
new = (params or {}).get("new_string", "")
|
|
837
|
+
if old and new:
|
|
838
|
+
diff = list(difflib.unified_diff(
|
|
839
|
+
old.splitlines(keepends=True),
|
|
840
|
+
new.splitlines(keepends=True),
|
|
841
|
+
lineterm="",
|
|
842
|
+
))
|
|
843
|
+
if diff:
|
|
844
|
+
from rich.syntax import Syntax
|
|
845
|
+
from rich.padding import Padding
|
|
846
|
+
diff_text = "".join(diff[2:])
|
|
847
|
+
console.print(Padding(Panel(
|
|
848
|
+
Syntax(diff_text, "diff", theme="monokai", padding=(0, 1)),
|
|
849
|
+
border_style="dim", box=rich_box.SIMPLE
|
|
850
|
+
), (0, 0, 0, 4)))
|
|
851
|
+
else:
|
|
852
|
+
ts_total = f" {total_elapsed:.1f}s" if total_elapsed >= 0.1 else ""
|
|
853
|
+
print(f"\n Activity · {n} tools{ts_total}")
|
|
854
|
+
for tool_name, result, elapsed, params in results:
|
|
855
|
+
icon, detail = _one_line_tool_summary(tool_name, result, elapsed, params)
|
|
856
|
+
detail_plain = re.sub(r"\[/?[^\]]+\]", "", detail)
|
|
857
|
+
icon_plain = "✓" if result.get("success") else "✗"
|
|
858
|
+
print(f" {tool_name:<18}{icon_plain} {detail_plain}")
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
# ── Fallback / model-switch toast ──────────────────────────────────────────────
|
|
862
|
+
|
|
863
|
+
def print_fallback_toast(
|
|
864
|
+
from_provider: str,
|
|
865
|
+
to_provider: str,
|
|
866
|
+
reason: str = "",
|
|
867
|
+
*,
|
|
868
|
+
console,
|
|
869
|
+
has_rich: bool,
|
|
870
|
+
) -> None:
|
|
871
|
+
"""Show a transient yellow notification when the active model/provider switches."""
|
|
872
|
+
if not has_rich:
|
|
873
|
+
print(f"\n ⚡ 模型切换 {from_provider} → {to_provider}{(' ' + reason) if reason else ''}")
|
|
874
|
+
return
|
|
875
|
+
body = f"[bold #C08050]⚡[/bold #C08050] [#C08050]{from_provider}[/#C08050] [dim]→[/dim] [#C08050]{to_provider}[/#C08050]"
|
|
876
|
+
if reason:
|
|
877
|
+
body += f"\n [dim]{reason}[/dim]"
|
|
878
|
+
console.print(f"\n {body}")
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
# ── Context pressure warning ───────────────────────────────────────────────────
|
|
882
|
+
|
|
883
|
+
_CTX_WARNED: dict[str, float] = {} # session_id → last warn time
|
|
884
|
+
|
|
885
|
+
def print_context_warning(
|
|
886
|
+
est_tokens: int,
|
|
887
|
+
max_tokens: int,
|
|
888
|
+
*,
|
|
889
|
+
console,
|
|
890
|
+
has_rich: bool,
|
|
891
|
+
session_id: str = "",
|
|
892
|
+
cooldown: float = 120.0, # only warn once every 2 min per session
|
|
893
|
+
) -> None:
|
|
894
|
+
"""Warn when context is >85% full; rate-limited to avoid spam."""
|
|
895
|
+
if max_tokens <= 0:
|
|
896
|
+
return
|
|
897
|
+
ratio = est_tokens / max_tokens
|
|
898
|
+
if ratio < 0.85:
|
|
899
|
+
return
|
|
900
|
+
now = time.monotonic()
|
|
901
|
+
if now - _CTX_WARNED.get(session_id, 0) < cooldown:
|
|
902
|
+
return
|
|
903
|
+
_CTX_WARNED[session_id] = now
|
|
904
|
+
|
|
905
|
+
def _k(n: int) -> str:
|
|
906
|
+
return f"{n // 1000}K" if n >= 1000 else str(n)
|
|
907
|
+
|
|
908
|
+
pct = int(ratio * 100)
|
|
909
|
+
if has_rich:
|
|
910
|
+
color = "red" if ratio >= 0.95 else "#C08050"
|
|
911
|
+
icon = "●" if ratio >= 0.95 else "⚠"
|
|
912
|
+
msg = f" [{color}]{icon} 上下文 {pct}% 已满 ({_k(est_tokens)}/{_k(max_tokens)} tokens)[/{color}]"
|
|
913
|
+
msg += " [dim]→ /compact 压缩历史 /clear 重置[/dim]"
|
|
914
|
+
console.print(msg)
|
|
915
|
+
else:
|
|
916
|
+
print(f" ⚠ 上下文 {pct}% ({_k(est_tokens)}/{_k(max_tokens)} tokens) — /compact 或 /clear")
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
# ── Blocked / cancelled tool visual ───────────────────────────────────────────
|
|
920
|
+
|
|
921
|
+
def print_tool_blocked(
|
|
922
|
+
tool_name: str,
|
|
923
|
+
reason: str = "用户取消",
|
|
924
|
+
*,
|
|
925
|
+
console,
|
|
926
|
+
has_rich: bool,
|
|
927
|
+
) -> None:
|
|
928
|
+
"""Show a styled 'Blocked' line when tool execution is denied or cancelled."""
|
|
929
|
+
if has_rich:
|
|
930
|
+
console.print(
|
|
931
|
+
f" [dim]⎿[/dim] [#C08050]⊘ {tool_name}[/#C08050] [dim]{reason}[/dim]"
|
|
932
|
+
)
|
|
933
|
+
else:
|
|
934
|
+
print(f" ⎿ ⊘ {tool_name} {reason}")
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
# ── Robot thinking / response header ──────────────────────────────────────────
|
|
938
|
+
|
|
939
|
+
def print_thinking_header(*, console, has_rich: bool, agent_name: str = "Aria", color: str = "#C08050") -> None:
|
|
940
|
+
"""Print a subtle copper 'Aria ▸' header before each AI response stream.
|
|
941
|
+
|
|
942
|
+
Gives the response a clear starting-point rather than appearing inline.
|
|
943
|
+
Called once per turn, right before the first streaming token is printed.
|
|
944
|
+
"""
|
|
945
|
+
if not has_rich:
|
|
946
|
+
return
|
|
947
|
+
console.print(f"[bold {color}]▣[/bold {color}] [dim {color}]{agent_name}[/dim {color}]", end=" ")
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
def print_done_footer(elapsed: float, *, console, has_rich: bool) -> None:
|
|
951
|
+
"""Print a dim elapsed-time line after the response stream ends."""
|
|
952
|
+
if not has_rich:
|
|
953
|
+
return
|
|
954
|
+
console.print(f"\n[dim] ✓ {elapsed:.1f}s[/dim]")
|