aria-code 0.52.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.
- aria_code/__init__.py +42 -0
- aria_code/_version.py +20 -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 +34 -0
- aria_code/agents/base.py +318 -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 +303 -0
- aria_code/agents/financial/fundamental.py +191 -0
- aria_code/agents/financial/macro.py +99 -0
- aria_code/agents/financial/news.py +207 -0
- aria_code/agents/financial/risk.py +165 -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 +258 -0
- aria_code/agents/orchestrator.py +44 -0
- aria_code/agents/portfolio_agent.py +363 -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 +158 -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 +492 -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/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 +15 -0
- aria_code/apps/channels/__init__.py +17 -0
- aria_code/apps/channels/intake.py +92 -0
- aria_code/apps/channels/registry.py +95 -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/bootstrap.py +145 -0
- aria_code/apps/cli/broker_render.py +206 -0
- aria_code/apps/cli/codegen_paths.py +29 -0
- aria_code/apps/cli/commands/__init__.py +16 -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 +369 -0
- aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
- aria_code/apps/cli/commands/auth_cmds.py +239 -0
- aria_code/apps/cli/commands/backtest_cmds.py +2543 -0
- aria_code/apps/cli/commands/broker_cmds.py +1234 -0
- aria_code/apps/cli/commands/business_workflow_cmds.py +319 -0
- aria_code/apps/cli/commands/canvas_cmds.py +77 -0
- aria_code/apps/cli/commands/catalog.py +87 -0
- aria_code/apps/cli/commands/core_cmds.py +2520 -0
- aria_code/apps/cli/commands/data_cmds.py +457 -0
- aria_code/apps/cli/commands/diagnostic_cmds.py +230 -0
- aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -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 +434 -0
- aria_code/apps/cli/commands/market_cmds.py +1381 -0
- aria_code/apps/cli/commands/market_context.py +425 -0
- aria_code/apps/cli/commands/market_render.py +7 -0
- aria_code/apps/cli/commands/model_cmds.py +2153 -0
- aria_code/apps/cli/commands/ops_cmds.py +721 -0
- aria_code/apps/cli/commands/orchestrator_cmds.py +179 -0
- aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
- aria_code/apps/cli/commands/portfolio_cmds.py +1278 -0
- aria_code/apps/cli/commands/report.py +437 -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 +669 -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 +79 -0
- aria_code/apps/cli/commands/workflow_cmds.py +480 -0
- aria_code/apps/cli/commands/workspace_cmds.py +1494 -0
- aria_code/apps/cli/config_paths.py +73 -0
- aria_code/apps/cli/config_store.py +78 -0
- aria_code/apps/cli/constants.py +88 -0
- aria_code/apps/cli/context.py +34 -0
- aria_code/apps/cli/deterministic.py +191 -0
- aria_code/apps/cli/direct.py +48 -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 +196 -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 +1309 -0
- aria_code/apps/cli/handlers/market_handlers.py +2513 -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/helpers.py +302 -0
- aria_code/apps/cli/hooks.py +180 -0
- aria_code/apps/cli/i18n.py +297 -0
- aria_code/apps/cli/intent.py +136 -0
- aria_code/apps/cli/intent_router.py +276 -0
- aria_code/apps/cli/intent_signals.py +344 -0
- aria_code/apps/cli/lifecycle_hooks.py +48 -0
- aria_code/apps/cli/local_tool_schemas.py +536 -0
- aria_code/apps/cli/main.py +29 -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/model_catalog.py +620 -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 +59 -0
- aria_code/apps/cli/prompts/__init__.py +0 -0
- aria_code/apps/cli/prompts/coding.py +53 -0
- aria_code/apps/cli/prompts/select.py +142 -0
- aria_code/apps/cli/prompts/system_prompts.py +552 -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 +521 -0
- aria_code/apps/cli/providers/chat_routing.py +128 -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 +1594 -0
- aria_code/apps/cli/providers/llm/response_cache.py +60 -0
- aria_code/apps/cli/providers/llm/sse_stream.py +257 -0
- aria_code/apps/cli/providers/runtime_bridge.py +353 -0
- aria_code/apps/cli/providers/vertexai_stream.py +316 -0
- aria_code/apps/cli/runtime_consumer.py +657 -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_store.py +112 -0
- aria_code/apps/cli/skills_catalog.py +273 -0
- aria_code/apps/cli/todo_tracker.py +190 -0
- aria_code/apps/cli/tool_executor.py +823 -0
- aria_code/apps/cli/tool_registry.py +34 -0
- aria_code/apps/cli/tool_scope.py +106 -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 +180 -0
- aria_code/apps/cli/tools/market_tools.py +620 -0
- aria_code/apps/cli/tools/notebook_tools.py +111 -0
- aria_code/apps/cli/tools/system_tools.py +726 -0
- aria_code/apps/cli/tools/write_tools.py +845 -0
- aria_code/apps/cli/tradingview_bridge.py +529 -0
- aria_code/apps/cli/turn_planning.py +137 -0
- aria_code/apps/cli/update_check.py +153 -0
- aria_code/apps/cli/utils/__init__.py +0 -0
- aria_code/apps/cli/utils/market_detect.py +1597 -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 +6743 -0
- aria_code/aria_daemon.py +1398 -0
- aria_code/aria_feishu_bot.py +1423 -0
- aria_code/aria_mcp_server.py +7 -0
- aria_code/aria_relay_client.py +4 -0
- aria_code/aria_relay_server.py +490 -0
- aria_code/aria_telegram_bot.py +202 -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 +182 -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 +1976 -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 +181 -0
- aria_code/command_safety.py +19 -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/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/demo_player.py +207 -0
- aria_code/doctor.py +813 -0
- aria_code/domain/__init__.py +0 -0
- aria_code/domain/backtest_engine.py +354 -0
- aria_code/domain/backtest_report.py +472 -0
- aria_code/domain/finance_formulas.py +389 -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/harness.py +598 -0
- aria_code/evals/runner.py +238 -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 +386 -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 +639 -0
- aria_code/memory_manager.py +246 -0
- aria_code/model_capability.py +486 -0
- aria_code/notification_tools.py +4 -0
- aria_code/openai_image_client.py +4 -0
- aria_code/packages/__init__.py +23 -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 +52 -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 +201 -0
- aria_code/packages/aria_mcp/circuit.py +82 -0
- aria_code/packages/aria_mcp/config.py +111 -0
- aria_code/packages/aria_mcp/installer.py +95 -0
- aria_code/packages/aria_mcp/server.py +1550 -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 +396 -0
- aria_code/packages/aria_sdk/providers.py +70 -0
- aria_code/packages/aria_sdk/streaming.py +79 -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 +268 -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 +57 -0
- aria_code/packages/aria_services/research_quality.py +186 -0
- aria_code/packages/aria_services/settings.py +157 -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 +8 -0
- aria_code/packages/aria_tools/financial/_paths.py +130 -0
- aria_code/packages/aria_tools/financial/compliance.py +31 -0
- aria_code/packages/aria_tools/financial/factors.py +33 -0
- aria_code/packages/aria_tools/financial/risk_tools.py +33 -0
- aria_code/packages/aria_tools/financial/strategy.py +31 -0
- aria_code/packages/aria_tools/registry.py +128 -0
- aria_code/packages/quant_engine/__init__.py +34 -0
- aria_code/packages/quant_engine/_namespace.py +62 -0
- aria_code/packages/quant_engine/backtest/__init__.py +14 -0
- aria_code/packages/quant_engine/backtest/core.py +53 -0
- aria_code/packages/quant_engine/backtest/engine.py +96 -0
- aria_code/packages/quant_engine/portfolio/__init__.py +13 -0
- aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
- aria_code/packages/quant_engine/sports/__init__.py +80 -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/ml_model.py +377 -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 +35 -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/logistics/__init__.py +169 -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/llm/__init__.py +19 -0
- aria_code/providers/llm/anthropic.py +184 -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 +129 -0
- aria_code/providers/llm/openai_compat.py +313 -0
- aria_code/providers/llm/registry.py +444 -0
- aria_code/providers/local_image_provider.py +202 -0
- aria_code/providers/local_llm_provider.py +589 -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/runtime/__init__.py +169 -0
- aria_code/runtime/acceptance.py +481 -0
- aria_code/runtime/agent_loop.py +1520 -0
- aria_code/runtime/approval.py +57 -0
- aria_code/runtime/budget.py +232 -0
- aria_code/runtime/checkpoints.py +432 -0
- aria_code/runtime/crash_report.py +174 -0
- aria_code/runtime/events.py +113 -0
- aria_code/runtime/gateway.py +150 -0
- aria_code/runtime/lsp.py +484 -0
- aria_code/runtime/orchestrator.py +55 -0
- aria_code/runtime/repo_map.py +700 -0
- aria_code/runtime/run_state.py +127 -0
- aria_code/runtime/run_store.py +450 -0
- aria_code/runtime/self_healing.py +422 -0
- aria_code/runtime/subagent.py +547 -0
- aria_code/runtime/task_ledger.py +86 -0
- aria_code/runtime/task_orchestrator.py +90 -0
- aria_code/runtime/tool_executor.py +217 -0
- aria_code/runtime/tool_policy.py +107 -0
- aria_code/runtime/worktrees.py +215 -0
- aria_code/runway_video_client.py +4 -0
- aria_code/safety/__init__.py +23 -0
- aria_code/safety/permissions.py +306 -0
- aria_code/safety/service.py +102 -0
- aria_code/setup_wizard.py +673 -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 +504 -0
- aria_code/tools/data_analysis_tools.py +809 -0
- aria_code/tools/enterprise_finance_tools.py +156 -0
- aria_code/tools/extended_tools.py +240 -0
- aria_code/tools/file_analysis_tools.py +885 -0
- aria_code/tools/image_gen_tools.py +114 -0
- aria_code/tools/local_finance_tools.py +3417 -0
- aria_code/tools/logistics_tools.py +135 -0
- aria_code/tools/macro_tools.py +369 -0
- aria_code/tools/notification_tools.py +249 -0
- aria_code/tools/project_tools.py +525 -0
- aria_code/tools/realty_data_tools.py +659 -0
- aria_code/tools/spreadsheet_tools.py +388 -0
- aria_code/tools/stripe_tools.py +115 -0
- aria_code/ui/__init__.py +105 -0
- aria_code/ui/banner.py +508 -0
- aria_code/ui/completer.py +501 -0
- aria_code/ui/console.py +281 -0
- aria_code/ui/image_render.py +243 -0
- aria_code/ui/input_box.py +488 -0
- aria_code/ui/picker.py +201 -0
- aria_code/ui/render/__init__.py +11 -0
- aria_code/ui/render/finance.py +1705 -0
- aria_code/ui/render/market.py +225 -0
- aria_code/ui/render/output.py +775 -0
- aria_code/ui/render/responsive.py +75 -0
- aria_code/ui/render/team.py +354 -0
- aria_code/ui/robot.py +235 -0
- aria_code/ui/startup_dashboard.py +109 -0
- aria_code/utils/vscode_uri.py +37 -0
- aria_code/video_analysis.py +147 -0
- aria_code/video_editor.py +271 -0
- aria_code/workspace/__init__.py +6 -0
- aria_code/workspace/files.py +193 -0
- aria_code/workspace/verify.py +113 -0
- aria_code/workspace_changes.py +199 -0
- aria_code/workspace_context.py +233 -0
- aria_code/workspace_service.py +196 -0
- aria_code/workspace_tools.py +150 -0
- aria_code-0.52.0.dist-info/METADATA +237 -0
- aria_code-0.52.0.dist-info/RECORD +478 -0
- aria_code-0.52.0.dist-info/WHEEL +5 -0
- aria_code-0.52.0.dist-info/entry_points.txt +3 -0
- aria_code-0.52.0.dist-info/licenses/LICENSE +201 -0
- aria_code-0.52.0.dist-info/licenses/NOTICE +49 -0
- aria_code-0.52.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import asyncio
|
|
6
|
+
import pathlib
|
|
7
|
+
import subprocess
|
|
8
|
+
import os
|
|
9
|
+
import re as re_module
|
|
10
|
+
import time
|
|
11
|
+
import sys
|
|
12
|
+
import re
|
|
13
|
+
from typing import Dict, Any, Optional
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
|
|
16
|
+
from aria_code.runtime import ApprovalDecision, apply_approval_decision
|
|
17
|
+
from aria_code.runtime.tool_policy import check_tool_policy
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from rich import box as rich_box
|
|
21
|
+
from rich.panel import Panel
|
|
22
|
+
except ImportError: # The legacy CLI injects these when Rich is available.
|
|
23
|
+
rich_box = None
|
|
24
|
+
Panel = None
|
|
25
|
+
|
|
26
|
+
# This module is imported by ``aria_cli`` and its public functions are rebound
|
|
27
|
+
# to that module's globals for backward compatibility. Keep safe defaults so
|
|
28
|
+
# the extracted module can still be imported, linted and type-checked on its
|
|
29
|
+
# own. The defaults are deliberately conservative; interactive execution uses
|
|
30
|
+
# the values owned by ``aria_cli`` after rebinding.
|
|
31
|
+
_ACTIVE_NETWORK_ENABLED = [True]
|
|
32
|
+
_ARIA_BOT_MODE = False
|
|
33
|
+
_CONFIRM_TOOLS: set[str] = set()
|
|
34
|
+
_HAS_JSON_HOOKS = False
|
|
35
|
+
_JSON_HOOKS: dict[str, object] = {}
|
|
36
|
+
# Real values, not placeholders. These two used to be empty stubs here and were
|
|
37
|
+
# defined for real in aria_cli.py, so execute_aria_tool only cached when it ran
|
|
38
|
+
# as the rebound copy with aria_cli's globals — and a direct import of it
|
|
39
|
+
# silently got a TTL table with nothing in it, i.e. no caching at all. aria_cli
|
|
40
|
+
# imports them from here now, so both copies share one cache and one config.
|
|
41
|
+
_CACHE_TTL: dict[str, int] = {
|
|
42
|
+
"get_market_data": 30, "get_market_history": 300, "get_crypto_data": 30, "get_forex_data": 30,
|
|
43
|
+
"get_commodities_data": 60, "get_bonds_data": 60, "get_futures_data": 60,
|
|
44
|
+
"get_news": 300, "get_sector_performance": 60, "get_market_overview": 60,
|
|
45
|
+
}
|
|
46
|
+
_TOOL_CACHE: dict[str, tuple] = {} # key -> (result, timestamp)
|
|
47
|
+
_auto_approve_session = False
|
|
48
|
+
_session_always_allow: set[str] = set()
|
|
49
|
+
_session_command_prefixes: set[tuple[str, ...]] = set()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _command_matches_session_prefix(command: str) -> bool:
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _command_approval_prefix(command: str) -> tuple[str, ...]:
|
|
57
|
+
return ()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _arrow_select(options, selected: int = 0, title: str = "") -> int:
|
|
61
|
+
return len(options) - 1
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _fire_json_hook(*args, **kwargs) -> bool:
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
from aria_code.apps.cli.tools.system_tools import (
|
|
68
|
+
tool_run_command as _src_run_command,
|
|
69
|
+
tool_web_fetch as _src_web_fetch,
|
|
70
|
+
tool_github as _src_github,
|
|
71
|
+
)
|
|
72
|
+
from aria_code.apps.cli.tools.notebook_tools import (
|
|
73
|
+
tool_glob as _src_glob,
|
|
74
|
+
tool_notebook_read as _src_notebook_read,
|
|
75
|
+
tool_notebook_edit as _src_notebook_edit,
|
|
76
|
+
)
|
|
77
|
+
from aria_code.apps.cli.tools.file_tools import (
|
|
78
|
+
tool_read_file as _src_read_file,
|
|
79
|
+
tool_list_files as _src_list_files,
|
|
80
|
+
tool_search_code as _src_search_code,
|
|
81
|
+
)
|
|
82
|
+
from aria_code.apps.cli.tools.market_tools import (
|
|
83
|
+
tool_get_market_data as _src_get_market_data,
|
|
84
|
+
tool_get_market_history as _src_get_market_history,
|
|
85
|
+
tool_broker_query as _src_broker_query,
|
|
86
|
+
tool_broker_order as _src_broker_order,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _g(name):
|
|
93
|
+
from aria_code import aria_cli
|
|
94
|
+
return getattr(aria_cli, name)
|
|
95
|
+
|
|
96
|
+
def _tool_analyze_file(params: dict) -> dict:
|
|
97
|
+
"""Parse & analyse a local document/image (pdf/docx/xlsx/csv/image/…)."""
|
|
98
|
+
from aria_code.file_analysis_tools import tool_analyze_file as _f
|
|
99
|
+
return _f(params)
|
|
100
|
+
def _tool_read_file(params: dict) -> dict:
|
|
101
|
+
"""Thin shim — implementation in apps/cli/tools/file_tools.py."""
|
|
102
|
+
return _src_read_file(params)
|
|
103
|
+
def _strip_markdown_fences(content: str) -> str:
|
|
104
|
+
"""Thin shim — implementation in apps/cli/tools/write_tools.py."""
|
|
105
|
+
from aria_code.apps.cli.tools.write_tools import _strip_markdown_fences as _f
|
|
106
|
+
return _f(content)
|
|
107
|
+
def _auto_fix_python(content: str, path: str) -> str:
|
|
108
|
+
"""Thin shim — implementation in apps/cli/tools/write_tools.py."""
|
|
109
|
+
from aria_code.apps.cli.tools.write_tools import _auto_fix_python as _f
|
|
110
|
+
return _f(content, path)
|
|
111
|
+
def _write_policy_confirm(p: pathlib.Path, content: str, existed: bool) -> tuple:
|
|
112
|
+
"""Thin shim — implementation in apps/cli/tools/write_tools.py."""
|
|
113
|
+
from aria_code.apps.cli.tools.write_tools import _write_policy_confirm as _f
|
|
114
|
+
return _f(p, content, existed)
|
|
115
|
+
def _tool_write_file(params: dict) -> dict:
|
|
116
|
+
"""Thin shim — implementation in apps/cli/tools/write_tools.py."""
|
|
117
|
+
from aria_code.apps.cli.tools.write_tools import tool_write_file as _f
|
|
118
|
+
return _f(params)
|
|
119
|
+
def _tool_edit_file(params: dict) -> dict:
|
|
120
|
+
"""Thin shim — implementation in apps/cli/tools/write_tools.py."""
|
|
121
|
+
from aria_code.apps.cli.tools.write_tools import tool_edit_file as _f
|
|
122
|
+
return _f(params)
|
|
123
|
+
def _tool_multi_edit(params: dict) -> dict:
|
|
124
|
+
"""Thin shim — implementation in apps/cli/tools/write_tools.py."""
|
|
125
|
+
from aria_code.apps.cli.tools.write_tools import tool_multi_edit as _f
|
|
126
|
+
return _f(params)
|
|
127
|
+
def _tool_update_todos(params: dict) -> dict:
|
|
128
|
+
"""Thin shim — implementation in apps/cli/todo_tracker.py."""
|
|
129
|
+
from aria_code.apps.cli.todo_tracker import update_todos as _f
|
|
130
|
+
return _f(params)
|
|
131
|
+
def _tool_list_files(params: dict) -> dict:
|
|
132
|
+
"""Thin shim — implementation in apps/cli/tools/file_tools.py."""
|
|
133
|
+
return _src_list_files(params)
|
|
134
|
+
def _tool_search_code(params: dict) -> dict:
|
|
135
|
+
"""Thin shim — implementation in apps/cli/tools/file_tools.py."""
|
|
136
|
+
return _src_search_code(params)
|
|
137
|
+
def _tool_run_command(params: dict) -> dict:
|
|
138
|
+
"""Run a shell command — thin wrapper supplying global defaults."""
|
|
139
|
+
params.setdefault("permission_mode", _g("_ACTIVE_PERMISSION_MODE")[0])
|
|
140
|
+
params.setdefault("network_enabled", _g("_ACTIVE_NETWORK_ENABLED")[0])
|
|
141
|
+
return _src_run_command(params, console=_g("console"), has_rich=_g("HAS_RICH"))
|
|
142
|
+
def _tool_web_fetch(params: dict) -> dict:
|
|
143
|
+
return _src_web_fetch(params)
|
|
144
|
+
def _tool_github(params: dict) -> dict:
|
|
145
|
+
params.setdefault("permission_mode", _g("_ACTIVE_PERMISSION_MODE")[0])
|
|
146
|
+
params.setdefault("network_enabled", _g("_ACTIVE_NETWORK_ENABLED")[0])
|
|
147
|
+
return _src_github(params, console=_g("console"), has_rich=_g("HAS_RICH"))
|
|
148
|
+
def _tool_glob(params: dict) -> dict:
|
|
149
|
+
return _src_glob(params)
|
|
150
|
+
def _tool_notebook_read(params: dict) -> dict:
|
|
151
|
+
return _src_notebook_read(params)
|
|
152
|
+
def _tool_notebook_edit(params: dict) -> dict:
|
|
153
|
+
return _src_notebook_edit(params)
|
|
154
|
+
def _tool_broker_query(params: dict) -> dict:
|
|
155
|
+
return _src_broker_query(params)
|
|
156
|
+
def _tool_broker_order(params: dict) -> dict:
|
|
157
|
+
return _src_broker_order(params)
|
|
158
|
+
def _tool_get_market_data(params: dict) -> dict:
|
|
159
|
+
return _src_get_market_data(params)
|
|
160
|
+
def _tool_get_market_history(params: dict) -> dict:
|
|
161
|
+
return _src_get_market_history(params)
|
|
162
|
+
def _todo_schema():
|
|
163
|
+
from aria_code.apps.cli.todo_tracker import UPDATE_TODOS_SCHEMA
|
|
164
|
+
return UPDATE_TODOS_SCHEMA
|
|
165
|
+
def _wrap_bare_schemas(bare: list) -> list:
|
|
166
|
+
"""Convert Anthropic-style {name, description, parameters} schemas into the
|
|
167
|
+
OpenAI {type:function, function:{…}} envelope used by _g("LOCAL_TOOL_SCHEMAS").
|
|
168
|
+
|
|
169
|
+
The subagent and LSP modules declare schemas in the bare form; without this
|
|
170
|
+
wrapping their tools execute but are invisible to the model (it is never
|
|
171
|
+
told they exist).
|
|
172
|
+
"""
|
|
173
|
+
wrapped = []
|
|
174
|
+
for s in bare or []:
|
|
175
|
+
if "function" in s: # already enveloped
|
|
176
|
+
wrapped.append(s)
|
|
177
|
+
else:
|
|
178
|
+
wrapped.append({"type": "function", "function": s})
|
|
179
|
+
return wrapped
|
|
180
|
+
def _dedup_tool_schemas() -> None:
|
|
181
|
+
"""Drop duplicate tool schemas, keeping the LAST occurrence by name.
|
|
182
|
+
|
|
183
|
+
Some tools (e.g. web_fetch, get_market_data) are declared both by the
|
|
184
|
+
finance-tools registry and the static schema block. The static block runs
|
|
185
|
+
after and carries the richer description, so keeping the last copy wins.
|
|
186
|
+
|
|
187
|
+
Schemas arrive in two shapes: the OpenAI-style ``{"type": "function",
|
|
188
|
+
"function": {...}}`` wrapper and the bare ``{"name", "description",
|
|
189
|
+
"parameters"}`` form that several register_* helpers emit. Reading only
|
|
190
|
+
the wrapped shape gave a bare schema an empty name, and the ``if name``
|
|
191
|
+
guard then dropped it — silently, and only from the schema list, so the
|
|
192
|
+
handler stayed in LOCAL_TOOLS while the model was never told the tool
|
|
193
|
+
existed. analyze_logistics_data, analyze_stripe_data and
|
|
194
|
+
analyze_financial_statements were all unreachable this way: registered,
|
|
195
|
+
callable, and invisible.
|
|
196
|
+
|
|
197
|
+
It also normalises, which is why it reads both shapes and writes one.
|
|
198
|
+
Leaving the bare form in the list sends Ollama and the OpenAI-compatible
|
|
199
|
+
providers a schema they reject, and forces every consumer to handle two
|
|
200
|
+
shapes — one test already assumed the wrapper and broke on the other.
|
|
201
|
+
"""
|
|
202
|
+
kept: dict = {}
|
|
203
|
+
for schema in _g("LOCAL_TOOL_SCHEMAS"):
|
|
204
|
+
name = (schema.get("function") or schema).get("name", "")
|
|
205
|
+
if not name:
|
|
206
|
+
continue # malformed: nothing can call it, nothing can describe it
|
|
207
|
+
kept[name] = schema if "function" in schema else {
|
|
208
|
+
"type": "function", "function": schema,
|
|
209
|
+
}
|
|
210
|
+
_g("LOCAL_TOOL_SCHEMAS")[:] = list(kept.values())
|
|
211
|
+
def _show_edit_preview(params: dict):
|
|
212
|
+
"""Show a diff preview for edit_file (Claude Code style, Panel-boxed)."""
|
|
213
|
+
if _g("_ARIA_BOT_MODE"):
|
|
214
|
+
return
|
|
215
|
+
path = params.get("path", "")
|
|
216
|
+
old_str = params.get("old_string", params.get("old_str", ""))
|
|
217
|
+
new_str = params.get("new_string", params.get("new_str", ""))
|
|
218
|
+
if not path or not old_str:
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
p = pathlib.Path(path).expanduser().resolve()
|
|
222
|
+
short = p.name or "file tool"
|
|
223
|
+
|
|
224
|
+
if not _g("HAS_RICH"):
|
|
225
|
+
print(f"\n Edit file {short}")
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
body_parts: list = []
|
|
229
|
+
try:
|
|
230
|
+
content = p.read_text(errors="replace")
|
|
231
|
+
pos = content.find(old_str)
|
|
232
|
+
if pos >= 0:
|
|
233
|
+
line_num = content[:pos].count("\n") + 1
|
|
234
|
+
all_lines = content.splitlines()
|
|
235
|
+
old_lines = old_str.splitlines()
|
|
236
|
+
new_lines = new_str.splitlines()
|
|
237
|
+
|
|
238
|
+
# Context before (up to 2 lines)
|
|
239
|
+
ctx_start = max(0, line_num - 3)
|
|
240
|
+
for i in range(ctx_start, line_num - 1):
|
|
241
|
+
if i < len(all_lines):
|
|
242
|
+
body_parts.append(f"[dim]{i+1:4} {all_lines[i][:100]}[/dim]")
|
|
243
|
+
|
|
244
|
+
# Removed lines
|
|
245
|
+
for i, ol in enumerate(old_lines):
|
|
246
|
+
ln = line_num + i
|
|
247
|
+
body_parts.append(f"[red]{ln:4} - {ol[:100]}[/red]")
|
|
248
|
+
|
|
249
|
+
# Added lines
|
|
250
|
+
for i, nl in enumerate(new_lines):
|
|
251
|
+
ln = line_num + i
|
|
252
|
+
body_parts.append(f"[green]{ln:4} + {nl[:100]}[/green]")
|
|
253
|
+
|
|
254
|
+
# Context after (up to 2 lines)
|
|
255
|
+
after_start = line_num - 1 + len(old_lines)
|
|
256
|
+
for i in range(after_start, min(after_start + 2, len(all_lines))):
|
|
257
|
+
body_parts.append(f"[dim]{i+1:4} {all_lines[i][:100]}[/dim]")
|
|
258
|
+
else:
|
|
259
|
+
# String not found — fallback to plain diff lines
|
|
260
|
+
for ol in old_str.splitlines()[:6]:
|
|
261
|
+
body_parts.append(f"[red]- {ol[:100]}[/red]")
|
|
262
|
+
for nl in new_str.splitlines()[:6]:
|
|
263
|
+
body_parts.append(f"[green]+ {nl[:100]}[/green]")
|
|
264
|
+
except Exception:
|
|
265
|
+
for ol in old_str.splitlines()[:6]:
|
|
266
|
+
body_parts.append(f"[red]- {ol[:100]}[/red]")
|
|
267
|
+
for nl in new_str.splitlines()[:6]:
|
|
268
|
+
body_parts.append(f"[green]+ {nl[:100]}[/green]")
|
|
269
|
+
|
|
270
|
+
_g("console").print()
|
|
271
|
+
_g("console").print(Panel(
|
|
272
|
+
"\n".join(body_parts) if body_parts else "[dim](no preview)[/dim]",
|
|
273
|
+
title=f"[yellow]Edit file[/yellow] [dim]{short}[/dim]",
|
|
274
|
+
title_align="left",
|
|
275
|
+
border_style="yellow",
|
|
276
|
+
box=rich_box.ROUNDED,
|
|
277
|
+
padding=(0, 1),
|
|
278
|
+
))
|
|
279
|
+
def _show_multi_edit_preview(params: dict):
|
|
280
|
+
"""Show a compact preview of all edits in a multi_edit call."""
|
|
281
|
+
if _g("_ARIA_BOT_MODE"):
|
|
282
|
+
return
|
|
283
|
+
path = params.get("path", "")
|
|
284
|
+
edits = params.get("edits", [])
|
|
285
|
+
if not path or not edits:
|
|
286
|
+
return
|
|
287
|
+
if not _g("HAS_RICH"):
|
|
288
|
+
print(f"\n Multi-edit {path} ({len(edits)} edits)")
|
|
289
|
+
return
|
|
290
|
+
body_parts: list = []
|
|
291
|
+
for i, ed in enumerate(edits):
|
|
292
|
+
old_s = ed.get("old_string", ed.get("old_str", "")) if isinstance(ed, dict) else ""
|
|
293
|
+
new_s = ed.get("new_string", ed.get("new_str", "")) if isinstance(ed, dict) else ""
|
|
294
|
+
body_parts.append(f"[bold]#{i+1}[/bold]")
|
|
295
|
+
for ol in old_s.splitlines()[:3]:
|
|
296
|
+
body_parts.append(f"[red] - {ol[:96]}[/red]")
|
|
297
|
+
for nl in new_s.splitlines()[:3]:
|
|
298
|
+
body_parts.append(f"[green] + {nl[:96]}[/green]")
|
|
299
|
+
_g("console").print()
|
|
300
|
+
_g("console").print(Panel(
|
|
301
|
+
"\n".join(body_parts) if body_parts else "[dim](no preview)[/dim]",
|
|
302
|
+
title=f"[yellow]Multi-edit[/yellow] [dim]{pathlib.Path(path).name} · {len(edits)} edits[/dim]",
|
|
303
|
+
title_align="left",
|
|
304
|
+
border_style="yellow",
|
|
305
|
+
box=rich_box.ROUNDED,
|
|
306
|
+
padding=(0, 1),
|
|
307
|
+
))
|
|
308
|
+
def _show_write_preview(params: dict):
|
|
309
|
+
"""Show a content preview for write_file (Claude Code style, Panel-boxed)."""
|
|
310
|
+
if _g("_ARIA_BOT_MODE"):
|
|
311
|
+
return
|
|
312
|
+
path = params.get("path", "")
|
|
313
|
+
content = params.get("content", "")
|
|
314
|
+
if not path:
|
|
315
|
+
return
|
|
316
|
+
# Show cleaned content (without markdown fences)
|
|
317
|
+
content = _strip_markdown_fences(content)
|
|
318
|
+
|
|
319
|
+
p = pathlib.Path(path).expanduser().resolve()
|
|
320
|
+
short = p.name or "file tool"
|
|
321
|
+
|
|
322
|
+
existed = p.exists()
|
|
323
|
+
action = "Overwrite file" if existed else "Write new file"
|
|
324
|
+
action_color = "yellow" if existed else "green"
|
|
325
|
+
lines = content.count("\n") + 1
|
|
326
|
+
|
|
327
|
+
if not _g("HAS_RICH"):
|
|
328
|
+
print(f"\n {action} {short} ({lines} lines)")
|
|
329
|
+
return
|
|
330
|
+
|
|
331
|
+
if lines > 80:
|
|
332
|
+
body_parts = [
|
|
333
|
+
f"[dim]Large generated file: {lines} lines[/dim]",
|
|
334
|
+
"[dim]Preview suppressed; the full content will be written as an artifact/file.[/dim]",
|
|
335
|
+
"[dim]Use /read after creation if you need to inspect it.[/dim]",
|
|
336
|
+
]
|
|
337
|
+
else:
|
|
338
|
+
preview_lines = content.splitlines()[:6]
|
|
339
|
+
body_parts = [f"[green]+ {pl[:100]}[/green]" for pl in preview_lines]
|
|
340
|
+
if lines > 6:
|
|
341
|
+
n = lines - 6
|
|
342
|
+
body_parts.append(f"[dim]… +{n} more line{'s' if n != 1 else ''}[/dim]")
|
|
343
|
+
body = "\n".join(body_parts)
|
|
344
|
+
|
|
345
|
+
_g("console").print()
|
|
346
|
+
_g("console").print(Panel(
|
|
347
|
+
body,
|
|
348
|
+
title=f"[{action_color}]{action}[/{action_color}] [dim]{short} ({lines} lines)[/dim]",
|
|
349
|
+
title_align="left",
|
|
350
|
+
border_style=action_color if existed else "dim",
|
|
351
|
+
box=rich_box.ROUNDED,
|
|
352
|
+
padding=(0, 1),
|
|
353
|
+
))
|
|
354
|
+
def _apply_tool_approval(params: dict, decision: ApprovalDecision) -> dict:
|
|
355
|
+
"""Apply approval state to CLI globals and execution params."""
|
|
356
|
+
# The two sets are only mutated in place, so only the rebound flag needs this.
|
|
357
|
+
global _auto_approve_session
|
|
358
|
+
if decision.auto_approve_session:
|
|
359
|
+
_auto_approve_session = True
|
|
360
|
+
if decision.tool_scope:
|
|
361
|
+
_session_always_allow.add(decision.tool_scope)
|
|
362
|
+
if decision.command_prefix:
|
|
363
|
+
_session_command_prefixes.add(tuple(decision.command_prefix))
|
|
364
|
+
return apply_approval_decision(params, decision)
|
|
365
|
+
def _confirm_tool_execution_decision(tool_name: str, params: dict,
|
|
366
|
+
config_policy: str = None) -> ApprovalDecision:
|
|
367
|
+
"""Ask user to confirm before executing a destructive tool.
|
|
368
|
+
Returns a structured approval decision.
|
|
369
|
+
|
|
370
|
+
For run_command: pre-flight policy check happens HERE, before showing the
|
|
371
|
+
picker. If the command would be blocked even with user approval (high-risk),
|
|
372
|
+
show error immediately. If medium-risk with 'safe' policy, offer to upgrade
|
|
373
|
+
policy inline so the user can act without leaving the flow.
|
|
374
|
+
"""
|
|
375
|
+
if config_policy is None:
|
|
376
|
+
config_policy = _g("_ACTIVE_COMMAND_POLICY")[0]
|
|
377
|
+
# ── Persistent per-tool policy check (allowlist / denylist) ─────────────
|
|
378
|
+
_policy_verdict = check_tool_policy(tool_name)
|
|
379
|
+
if _policy_verdict == "deny":
|
|
380
|
+
if _g("HAS_RICH"):
|
|
381
|
+
_g("console").print(
|
|
382
|
+
f" [red]✗ 工具 '{tool_name}' 被永久黑名单拒绝[/red] "
|
|
383
|
+
f"[dim](/config policy remove {tool_name} 可解除)[/dim]"
|
|
384
|
+
)
|
|
385
|
+
else:
|
|
386
|
+
print(f" ✗ '{tool_name}' blocked by tool policy")
|
|
387
|
+
return ApprovalDecision.deny("blocked by tool policy (deny list)")
|
|
388
|
+
if _policy_verdict == "allow":
|
|
389
|
+
if tool_name == "run_command":
|
|
390
|
+
return ApprovalDecision.allow(policy=config_policy, user_approved=True)
|
|
391
|
+
return ApprovalDecision.allow()
|
|
392
|
+
|
|
393
|
+
if _auto_approve_session:
|
|
394
|
+
# Still inject policy so run_command doesn't re-block
|
|
395
|
+
if tool_name == "run_command":
|
|
396
|
+
return ApprovalDecision.allow(policy=config_policy, user_approved=True)
|
|
397
|
+
return ApprovalDecision.allow()
|
|
398
|
+
if tool_name == "run_command" and _command_matches_session_prefix(params.get("command", "")):
|
|
399
|
+
return ApprovalDecision.allow(policy="balanced", user_approved=True)
|
|
400
|
+
# Per-tool session allow — user previously chose "Always allow [tool] this session"
|
|
401
|
+
if tool_name in _session_always_allow:
|
|
402
|
+
if tool_name == "run_command":
|
|
403
|
+
return ApprovalDecision.allow(policy=config_policy, user_approved=True)
|
|
404
|
+
return ApprovalDecision.allow()
|
|
405
|
+
|
|
406
|
+
# ── Plan mode: confirm ALL tools (even non-CONFIRM_TOOLS) ────────────────
|
|
407
|
+
if _g("_PLAN_MODE").active and tool_name not in _CONFIRM_TOOLS:
|
|
408
|
+
return _g("_PLAN_MODE").confirm_step(
|
|
409
|
+
tool_name, params,
|
|
410
|
+
console=_g("console"),
|
|
411
|
+
has_rich=_g("HAS_RICH"),
|
|
412
|
+
arrow_select_fn=_arrow_select,
|
|
413
|
+
)
|
|
414
|
+
# Ask-always policy: force confirmation even for normally silent tools
|
|
415
|
+
if _policy_verdict == "ask" and tool_name not in _CONFIRM_TOOLS:
|
|
416
|
+
_CONFIRM_TOOLS.add(tool_name) # promote for this call only
|
|
417
|
+
|
|
418
|
+
if tool_name not in _CONFIRM_TOOLS:
|
|
419
|
+
return ApprovalDecision.allow()
|
|
420
|
+
|
|
421
|
+
# ── JSON PreToolUse hook — can block execution ────────────────────────────
|
|
422
|
+
if _HAS_JSON_HOOKS and _JSON_HOOKS.get("PreToolUse"):
|
|
423
|
+
_allowed = _fire_json_hook(
|
|
424
|
+
"PreToolUse", tool=tool_name, params=params, hooks=_JSON_HOOKS,
|
|
425
|
+
)
|
|
426
|
+
if not _allowed:
|
|
427
|
+
return ApprovalDecision.deny("Blocked by PreToolUse hook")
|
|
428
|
+
|
|
429
|
+
# ── Pre-flight for run_command ────────────────────────────────────────────
|
|
430
|
+
if tool_name == "run_command":
|
|
431
|
+
from aria_code.safety import classify_command_risk
|
|
432
|
+
cmd = params.get("command", "")
|
|
433
|
+
if isinstance(cmd, list):
|
|
434
|
+
import shlex as _shlex_tmp
|
|
435
|
+
cmd = _shlex_tmp.join(str(c) for c in cmd)
|
|
436
|
+
params["command"] = cmd
|
|
437
|
+
risk = classify_command_risk(cmd)
|
|
438
|
+
|
|
439
|
+
if risk == "high":
|
|
440
|
+
# Always block high-risk regardless of user approval
|
|
441
|
+
if _g("HAS_RICH"):
|
|
442
|
+
_g("console").print(Panel(
|
|
443
|
+
f"[red]✗ 高风险命令已拒绝[/red]\n[dim]{cmd[:120]}[/dim]\n"
|
|
444
|
+
f"[dim]高风险操作(rm -rf / docker / sudo 等)需要在终端手动执行。[/dim]",
|
|
445
|
+
border_style="red", box=rich_box.ROUNDED, padding=(0, 1),
|
|
446
|
+
))
|
|
447
|
+
else:
|
|
448
|
+
print(f" ✗ 高风险命令已拒绝: {cmd[:80]}")
|
|
449
|
+
return ApprovalDecision.deny("high-risk command")
|
|
450
|
+
|
|
451
|
+
if risk == "medium" and config_policy == "safe":
|
|
452
|
+
# Show a richer picker that includes a "Allow & upgrade policy" option
|
|
453
|
+
if _g("HAS_RICH"):
|
|
454
|
+
_g("console").print()
|
|
455
|
+
_g("console").print(Panel(
|
|
456
|
+
f"[yellow]⚠ 此命令需要 balanced 策略(当前: safe)[/yellow]\n"
|
|
457
|
+
f"[dim]{cmd[:120]}[/dim]",
|
|
458
|
+
border_style="yellow", box=rich_box.ROUNDED, padding=(0, 1),
|
|
459
|
+
))
|
|
460
|
+
_prefix = _command_approval_prefix(cmd)
|
|
461
|
+
_prefix_label = " ".join(_prefix)[:72] if _prefix else cmd[:72]
|
|
462
|
+
options = [
|
|
463
|
+
("Allow once", "仅此次允许(不改变策略)"),
|
|
464
|
+
("Allow similar this session", f"本会话允许前缀: {_prefix_label}"),
|
|
465
|
+
("Allow & set balanced", "允许并升级策略(本会话有效)"),
|
|
466
|
+
("No", "拒绝执行"),
|
|
467
|
+
]
|
|
468
|
+
choice = _arrow_select(options, selected=0, title="")
|
|
469
|
+
if choice == 0:
|
|
470
|
+
return ApprovalDecision.allow(policy="balanced", user_approved=True)
|
|
471
|
+
if choice == 1:
|
|
472
|
+
return ApprovalDecision.allow(
|
|
473
|
+
policy="balanced",
|
|
474
|
+
user_approved=True,
|
|
475
|
+
command_prefix=_prefix,
|
|
476
|
+
)
|
|
477
|
+
if choice == 2:
|
|
478
|
+
return ApprovalDecision.allow(
|
|
479
|
+
policy="balanced",
|
|
480
|
+
user_approved=True,
|
|
481
|
+
upgrade_policy=True,
|
|
482
|
+
)
|
|
483
|
+
return ApprovalDecision.deny("user denied") # No
|
|
484
|
+
|
|
485
|
+
# ── Default confirmation for write_file / edit_file / low-risk run ────────
|
|
486
|
+
if tool_name == "edit_file":
|
|
487
|
+
_show_edit_preview(params)
|
|
488
|
+
elif tool_name == "multi_edit":
|
|
489
|
+
_show_multi_edit_preview(params)
|
|
490
|
+
elif tool_name == "write_file":
|
|
491
|
+
_show_write_preview(params)
|
|
492
|
+
elif tool_name == "run_command":
|
|
493
|
+
# Header already printed by on_tool_call — just pass through policy
|
|
494
|
+
pass
|
|
495
|
+
|
|
496
|
+
_tool_label = {"write_file": "写文件", "edit_file": "编辑文件", "multi_edit": "批量编辑", "run_command": "运行命令"}.get(tool_name, tool_name)
|
|
497
|
+
_command_prefix = _command_approval_prefix(params.get("command", "")) if tool_name == "run_command" else ()
|
|
498
|
+
_scope_label = (
|
|
499
|
+
f"Always allow {' '.join(_command_prefix)[:64]}"
|
|
500
|
+
if _command_prefix else f"Always allow {_tool_label}"
|
|
501
|
+
)
|
|
502
|
+
_scope_help = (
|
|
503
|
+
"本会话内允许相同命令前缀"
|
|
504
|
+
if _command_prefix else f"本会话内自动允许所有 {_tool_label}"
|
|
505
|
+
)
|
|
506
|
+
options = [
|
|
507
|
+
("Yes", ""),
|
|
508
|
+
(_scope_label, _scope_help),
|
|
509
|
+
("Yes, allow all tools", "本会话内所有工具自动允许"),
|
|
510
|
+
("No", ""),
|
|
511
|
+
]
|
|
512
|
+
choice = _arrow_select(options, selected=0, title="")
|
|
513
|
+
|
|
514
|
+
if choice == 0:
|
|
515
|
+
if tool_name == "run_command":
|
|
516
|
+
return ApprovalDecision.allow(policy=config_policy, user_approved=True)
|
|
517
|
+
return ApprovalDecision.allow()
|
|
518
|
+
if choice == 1:
|
|
519
|
+
return ApprovalDecision.allow(
|
|
520
|
+
policy=config_policy if tool_name == "run_command" else None,
|
|
521
|
+
user_approved=True,
|
|
522
|
+
command_prefix=_command_prefix,
|
|
523
|
+
tool_scope="" if _command_prefix else tool_name,
|
|
524
|
+
)
|
|
525
|
+
if choice == 2:
|
|
526
|
+
if tool_name == "run_command":
|
|
527
|
+
return ApprovalDecision.allow(
|
|
528
|
+
policy=config_policy,
|
|
529
|
+
user_approved=True,
|
|
530
|
+
auto_approve_session=True,
|
|
531
|
+
)
|
|
532
|
+
return ApprovalDecision.allow(auto_approve_session=True)
|
|
533
|
+
return ApprovalDecision.deny("user denied")
|
|
534
|
+
async def execute_aria_tool(base_url: str, tool_name: str, params: dict,
|
|
535
|
+
timeout: int = 30, auth_token: str = None,
|
|
536
|
+
max_retries: int = 2) -> dict:
|
|
537
|
+
"""Execute an Aria tool via the backend API with auto-retry and TTL cache."""
|
|
538
|
+
# --- Parameter validation before sending to API ---
|
|
539
|
+
_symbol_tools = {
|
|
540
|
+
"get_market_data", "get_risk_metrics", "calculate_factors",
|
|
541
|
+
"get_alpha158_factors", "assess_portfolio_risk",
|
|
542
|
+
}
|
|
543
|
+
_date_tools = {"backtest_strategy", "stress_test_strategy"}
|
|
544
|
+
|
|
545
|
+
if tool_name in _symbol_tools and "symbol" in params:
|
|
546
|
+
sym = str(params["symbol"]).strip().upper()
|
|
547
|
+
if not re_module.match(r'^[A-Z0-9.\-/=]{1,12}$', sym):
|
|
548
|
+
return {"success": False, "error": f"Invalid symbol format: '{sym}'"}
|
|
549
|
+
params = {**params, "symbol": sym}
|
|
550
|
+
|
|
551
|
+
if tool_name in _date_tools:
|
|
552
|
+
for date_key in ("start_date", "end_date", "start", "end"):
|
|
553
|
+
if date_key in params:
|
|
554
|
+
date_val = str(params[date_key]).strip()
|
|
555
|
+
if not re_module.match(r'^\d{4}-\d{2}-\d{2}$', date_val):
|
|
556
|
+
return {"success": False, "error": f"Invalid date format for '{date_key}': '{date_val}' (expected YYYY-MM-DD)"}
|
|
557
|
+
# Check chronological order
|
|
558
|
+
start = params.get("start_date") or params.get("start")
|
|
559
|
+
end = params.get("end_date") or params.get("end")
|
|
560
|
+
if start and end and start > end:
|
|
561
|
+
return {"success": False, "error": f"start_date ({start}) must be before end_date ({end})"}
|
|
562
|
+
|
|
563
|
+
# Check cache for read-only tools
|
|
564
|
+
ttl = _CACHE_TTL.get(tool_name)
|
|
565
|
+
if ttl:
|
|
566
|
+
cache_key = f"{tool_name}:{json.dumps(params, sort_keys=True)}"
|
|
567
|
+
cached = _TOOL_CACHE.get(cache_key)
|
|
568
|
+
if cached and (time.time() - cached[1]) < ttl:
|
|
569
|
+
return cached[0]
|
|
570
|
+
import aiohttp
|
|
571
|
+
url = f"{base_url}/api/aria/execute-tool"
|
|
572
|
+
payload = {"tool_name": tool_name, "params": params}
|
|
573
|
+
headers = {}
|
|
574
|
+
if auth_token:
|
|
575
|
+
headers["Authorization"] = f"Bearer {auth_token}"
|
|
576
|
+
last_error = None
|
|
577
|
+
for attempt in range(max_retries + 1):
|
|
578
|
+
try:
|
|
579
|
+
async with aiohttp.ClientSession() as session:
|
|
580
|
+
async with session.post(url, json=payload, headers=headers,
|
|
581
|
+
timeout=aiohttp.ClientTimeout(total=timeout)) as resp:
|
|
582
|
+
result = await resp.json()
|
|
583
|
+
if result.get("success") or attempt >= max_retries:
|
|
584
|
+
# Cache successful results for read-only tools
|
|
585
|
+
if result.get("success") and ttl:
|
|
586
|
+
_TOOL_CACHE[cache_key] = (result, time.time())
|
|
587
|
+
return result
|
|
588
|
+
last_error = result.get("error", "Unknown error")
|
|
589
|
+
except Exception as e:
|
|
590
|
+
last_error = str(e)
|
|
591
|
+
if attempt < max_retries:
|
|
592
|
+
await asyncio.sleep(1 * (attempt + 1)) # 1s, 2s backoff
|
|
593
|
+
return {"success": False, "error": f"Failed after {max_retries + 1} attempts: {last_error}"}
|
|
594
|
+
# Hard ceiling on one tool result fed back into the model, in characters.
|
|
595
|
+
# Individual branches below apply their own caps, but several produced
|
|
596
|
+
# unbounded text, and nothing capped the total: a turn could inject tens of
|
|
597
|
+
# thousands of characters of tool output into the context with no limit.
|
|
598
|
+
DEFAULT_TOOL_RESULT_CHAR_LIMIT = 4000
|
|
599
|
+
|
|
600
|
+
# Never truncate below this, or a "summary" degrades into an unusable stub.
|
|
601
|
+
MIN_TOOL_RESULT_CHAR_LIMIT = 400
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def truncate_tool_summary(summary: str, limit: int = DEFAULT_TOOL_RESULT_CHAR_LIMIT) -> str:
|
|
605
|
+
"""Clip a tool summary to *limit* characters, saying so explicitly.
|
|
606
|
+
|
|
607
|
+
The marker matters: silently dropping the tail makes a model treat a
|
|
608
|
+
partial result as complete. Head and tail are both kept because the useful
|
|
609
|
+
signal in command output and file content sits at the two ends — an error
|
|
610
|
+
traceback ends at the bottom, a file's structure shows at the top.
|
|
611
|
+
"""
|
|
612
|
+
text = summary if isinstance(summary, str) else str(summary)
|
|
613
|
+
limit = max(int(limit or 0), MIN_TOOL_RESULT_CHAR_LIMIT)
|
|
614
|
+
if len(text) <= limit:
|
|
615
|
+
return text
|
|
616
|
+
marker_for = (
|
|
617
|
+
lambda n: f"\n\n… [{n} characters truncated to fit the context budget] …\n\n"
|
|
618
|
+
)
|
|
619
|
+
# The marker counts against the budget, so the returned text never exceeds
|
|
620
|
+
# *limit* — a cap that its own notice can overshoot is not a cap.
|
|
621
|
+
budget = max(limit - len(marker_for(len(text))), MIN_TOOL_RESULT_CHAR_LIMIT // 2)
|
|
622
|
+
head = int(budget * 0.7)
|
|
623
|
+
tail = budget - head
|
|
624
|
+
kept = text[:head] + (text[-tail:] if tail > 0 else "")
|
|
625
|
+
return text[:head] + marker_for(len(text) - len(kept)) + (text[-tail:] if tail > 0 else "")
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def _format_tool_summary(
|
|
629
|
+
tool_name: str,
|
|
630
|
+
result: dict,
|
|
631
|
+
*,
|
|
632
|
+
char_limit: int = DEFAULT_TOOL_RESULT_CHAR_LIMIT,
|
|
633
|
+
) -> str:
|
|
634
|
+
"""Format a tool result into a concise, length-bounded follow-up summary."""
|
|
635
|
+
return truncate_tool_summary(_format_tool_summary_raw(tool_name, result), char_limit)
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def _format_tool_summary_raw(tool_name: str, result: dict) -> str:
|
|
639
|
+
"""Format tool result into a concise summary for AI follow-up context."""
|
|
640
|
+
if not result.get("success"):
|
|
641
|
+
return f"Error: {_g('_clean_tool_error_message')(result.get('error', 'failed'))}"
|
|
642
|
+
data = result.get("data", {})
|
|
643
|
+
|
|
644
|
+
# Local finance tools return their payload at the top level, whereas some
|
|
645
|
+
# remote adapters wrap it in ``data``. Treat both shapes consistently.
|
|
646
|
+
# Previously a successful local quote became ``{}`` here, leaving the LLM
|
|
647
|
+
# to invent a price/RSI despite the terminal having displayed real data.
|
|
648
|
+
tool_data = data if isinstance(data, dict) and data else result
|
|
649
|
+
if tool_name == "get_market_data":
|
|
650
|
+
symbol = tool_data.get("symbol", "")
|
|
651
|
+
price = tool_data.get("price", tool_data.get("latest_close"))
|
|
652
|
+
change = tool_data.get("change_pct")
|
|
653
|
+
provider = tool_data.get("provider", "unknown")
|
|
654
|
+
as_of = tool_data.get("as_of")
|
|
655
|
+
retrieved_at = tool_data.get("retrieved_at")
|
|
656
|
+
lines = [f"Market data for {symbol} (provider: {provider})"]
|
|
657
|
+
if price is not None:
|
|
658
|
+
currency = tool_data.get("currency", "")
|
|
659
|
+
lines.append(f"Price: {currency} {price}".strip())
|
|
660
|
+
if change is not None:
|
|
661
|
+
lines.append(f"Change: {change}%")
|
|
662
|
+
if as_of:
|
|
663
|
+
lines.append(f"Provider timestamp: {as_of}")
|
|
664
|
+
elif retrieved_at:
|
|
665
|
+
lines.append(f"Retrieved at: {retrieved_at} (provider did not supply trade timestamp)")
|
|
666
|
+
rsi = tool_data.get("rsi")
|
|
667
|
+
if rsi is not None:
|
|
668
|
+
try:
|
|
669
|
+
rsi_value = float(rsi)
|
|
670
|
+
rsi_label = "oversold" if rsi_value <= 30 else ("overbought" if rsi_value >= 70 else "neutral")
|
|
671
|
+
lines.append(f"RSI(14): {rsi_value:.2f} ({rsi_label})")
|
|
672
|
+
except (TypeError, ValueError):
|
|
673
|
+
lines.append("RSI(14): unavailable")
|
|
674
|
+
macd_hist = tool_data.get("macd_hist")
|
|
675
|
+
if macd_hist is not None:
|
|
676
|
+
lines.append(f"MACD histogram: {macd_hist}")
|
|
677
|
+
lines.append("Use only these values. Do not call this quote 'latest' beyond its stated timestamp.")
|
|
678
|
+
return "\n".join(lines)
|
|
679
|
+
|
|
680
|
+
if tool_name == "analyze_news":
|
|
681
|
+
news = tool_data.get("news") or []
|
|
682
|
+
provider = tool_data.get("provider", "unknown")
|
|
683
|
+
if not news:
|
|
684
|
+
return (
|
|
685
|
+
f"No relevant news returned (provider: {provider}). "
|
|
686
|
+
"Do not infer news sentiment; state that news evidence is unavailable."
|
|
687
|
+
)
|
|
688
|
+
lines = [f"News evidence (provider: {provider}, items: {len(news)}):"]
|
|
689
|
+
for item in news[:3]:
|
|
690
|
+
if not isinstance(item, dict):
|
|
691
|
+
continue
|
|
692
|
+
title = str(item.get("title") or "untitled")[:220]
|
|
693
|
+
published = str(item.get("time") or item.get("published_at") or "time unavailable")
|
|
694
|
+
publisher = str(item.get("publisher") or item.get("source") or "source unavailable")
|
|
695
|
+
lines.append(f"- {title} | {publisher} | {published}")
|
|
696
|
+
return "\n".join(lines)
|
|
697
|
+
|
|
698
|
+
if tool_name == "run_command":
|
|
699
|
+
exit_code = data.get("exit_code", -1)
|
|
700
|
+
stdout = data.get("stdout", "").strip()
|
|
701
|
+
stderr = data.get("stderr", "").strip()
|
|
702
|
+
out = f"exit_code={exit_code}"
|
|
703
|
+
if stdout:
|
|
704
|
+
out += f"\nstdout:\n{stdout[:2000]}"
|
|
705
|
+
if stderr and exit_code != 0:
|
|
706
|
+
out += f"\nstderr:\n{stderr[:500]}"
|
|
707
|
+
# Add actionable hints for common errors
|
|
708
|
+
if exit_code != 0:
|
|
709
|
+
combined = (stdout + " " + stderr).lower()
|
|
710
|
+
combined_raw = stdout + " " + stderr
|
|
711
|
+
if "can't open file" in combined or "no such file" in combined:
|
|
712
|
+
out += "\n\nHINT: The file does not exist. You must create it with write_file first, then run it."
|
|
713
|
+
elif "modulenotfounderror" in combined or "no module named" in combined:
|
|
714
|
+
# Extract module name
|
|
715
|
+
import re as _re
|
|
716
|
+
mod_match = _re.search(r"no module named ['\"]?(\w+)", combined)
|
|
717
|
+
mod_name = mod_match.group(1) if mod_match else "<module_name>"
|
|
718
|
+
out += f"\n\nHINT: Module '{mod_name}' is missing. Fix: run_command pip3 install {mod_name}, then run_command python3 to retry."
|
|
719
|
+
elif "nameerror" in combined:
|
|
720
|
+
# Extract the undefined name
|
|
721
|
+
import re as _re
|
|
722
|
+
name_match = _re.search(r"name ['\"](\w+)['\"] is not defined", combined_raw)
|
|
723
|
+
if name_match:
|
|
724
|
+
missing_name = name_match.group(1)
|
|
725
|
+
out += (f"\n\nHINT: '{missing_name}' is not defined — you forgot to import it. "
|
|
726
|
+
f"Use edit_file to add the missing import (e.g., 'import {missing_name}') at the top of the script, then retry.")
|
|
727
|
+
else:
|
|
728
|
+
out += "\n\nHINT: A variable or module is not defined. Use read_file to check imports, edit_file to add the missing import, then retry."
|
|
729
|
+
elif "syntaxerror" in combined:
|
|
730
|
+
import re as _re
|
|
731
|
+
line_match = _re.search(r"line (\d+)", combined)
|
|
732
|
+
line_hint = f" at line {line_match.group(1)}" if line_match else ""
|
|
733
|
+
out += f"\n\nHINT: Syntax error{line_hint}. Use read_file to see the code, then edit_file to fix the exact line, then retry."
|
|
734
|
+
elif "typeerror" in combined:
|
|
735
|
+
out += "\n\nHINT: Type error — wrong argument types or wrong number of arguments. Use read_file to inspect, edit_file to fix, then retry."
|
|
736
|
+
elif "keyerror" in combined or "indexerror" in combined:
|
|
737
|
+
# Special hint for yfinance MultiIndex KeyError
|
|
738
|
+
if any(col in combined_raw for col in ("'Close'", "'Open'", "'High'", "'Low'", "'Volume'")):
|
|
739
|
+
out += ("\n\nHINT: yfinance MultiIndex KeyError — yf.download() returns MultiIndex columns "
|
|
740
|
+
"when downloading multiple tickers. Fix: add `if isinstance(df.columns, pd.MultiIndex): "
|
|
741
|
+
"df.columns = df.columns.droplevel(1)` right after yf.download(). "
|
|
742
|
+
"Use edit_file to add this fix, then retry.")
|
|
743
|
+
else:
|
|
744
|
+
out += "\n\nHINT: Data structure mismatch. Use read_file to check the code logic. The data may have different column names or fewer elements than expected."
|
|
745
|
+
elif "attributeerror" in combined:
|
|
746
|
+
out += "\n\nHINT: Attribute error — the object doesn't have that method/property. Check the library version or API docs. Use read_file then edit_file to fix."
|
|
747
|
+
elif "valueerror" in combined:
|
|
748
|
+
out += "\n\nHINT: Value error — invalid value passed to a function. Use read_file to check the data types and fix with edit_file."
|
|
749
|
+
elif "permission denied" in combined:
|
|
750
|
+
out += "\n\nHINT: Permission denied. Try adding chmod +x, or run with python3 explicitly."
|
|
751
|
+
else:
|
|
752
|
+
out += "\n\nHINT: Script failed. Use read_file to inspect the code, find the error, edit_file to fix it, then run_command to retry. Do NOT give up."
|
|
753
|
+
else:
|
|
754
|
+
# Script succeeded — auto-verify and auto-open output files.
|
|
755
|
+
from aria_code.artifacts import user_generated_dir as _aria_user_generated_dir
|
|
756
|
+
output_dir = _aria_user_generated_dir()
|
|
757
|
+
try:
|
|
758
|
+
recent_files = []
|
|
759
|
+
for ext in ("*.png", "*.html", "*.csv", "*.pdf", "*.xlsx"):
|
|
760
|
+
for f in output_dir.rglob(ext):
|
|
761
|
+
if (time.time() - f.stat().st_mtime) < 30:
|
|
762
|
+
recent_files.append(f)
|
|
763
|
+
# Also detect files mentioned in stdout (e.g., "Saved to /path/to/file.png")
|
|
764
|
+
saved_pattern = re_module.findall(r'(?:saved?\s+(?:to|as|at)|wrote|output|created)[:\s]+([^\s\'"]+\.(?:png|html|csv|pdf))', stdout, re_module.IGNORECASE)
|
|
765
|
+
for sp in saved_pattern:
|
|
766
|
+
p = pathlib.Path(sp).expanduser().resolve()
|
|
767
|
+
if p.exists() and p not in recent_files:
|
|
768
|
+
recent_files.append(p)
|
|
769
|
+
if recent_files:
|
|
770
|
+
names = [f.name for f in recent_files]
|
|
771
|
+
out += f"\n\nVerified: output files created: {', '.join(names)}"
|
|
772
|
+
# Auto-open on macOS (non-blocking)
|
|
773
|
+
for f in recent_files[:3]:
|
|
774
|
+
try:
|
|
775
|
+
subprocess.Popen(["open", str(f)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
776
|
+
except Exception:
|
|
777
|
+
pass
|
|
778
|
+
if _g("HAS_RICH"):
|
|
779
|
+
_g("console").print(f" [dim]Opened {', '.join(names[:3])}[/dim]")
|
|
780
|
+
else:
|
|
781
|
+
combined_check = (stdout + " " + stderr).lower()
|
|
782
|
+
if any(kw in combined_check for kw in ("chart", "plot", "figure", "savefig", "save")):
|
|
783
|
+
out += ("\n\nWARNING: Script ran but no output files detected in the Aria generated output directory. "
|
|
784
|
+
"Check the save path uses ~/Documents/Aria Code/generated.")
|
|
785
|
+
except Exception:
|
|
786
|
+
pass
|
|
787
|
+
return out
|
|
788
|
+
if tool_name == "write_file":
|
|
789
|
+
_base = f"OK: {data.get('action', 'created')} {data.get('path', '')} ({data.get('lines', 0)} lines)"
|
|
790
|
+
if result.get("warning"):
|
|
791
|
+
_base += f"\n\n{result['warning']}"
|
|
792
|
+
return _base
|
|
793
|
+
if tool_name == "edit_file":
|
|
794
|
+
_base = f"OK: edited {data.get('path', '')} ({data.get('replacements', 0)} replacements)"
|
|
795
|
+
if result.get("warning"):
|
|
796
|
+
_base += f"\n\n{result['warning']}"
|
|
797
|
+
return _base
|
|
798
|
+
if tool_name == "multi_edit":
|
|
799
|
+
_base = f"OK: {data.get('edits_applied', 0)} edits applied to {data.get('path', '')}"
|
|
800
|
+
if result.get("warning"):
|
|
801
|
+
_base += f"\n\n{result['warning']}"
|
|
802
|
+
return _base
|
|
803
|
+
if tool_name == "update_todos":
|
|
804
|
+
_todos = data.get("todos", [])
|
|
805
|
+
_lines = [f"任务进度 {data.get('completed', 0)}/{data.get('total', len(_todos))}:"]
|
|
806
|
+
_mark = {"completed": "[x]", "in_progress": "[>]", "pending": "[ ]"}
|
|
807
|
+
for _t in _todos:
|
|
808
|
+
_lines.append(f" {_mark.get(_t.get('status'), '[ ]')} {_t.get('content', '')}")
|
|
809
|
+
return "\n".join(_lines)
|
|
810
|
+
if tool_name == "read_file":
|
|
811
|
+
content = data.get("content", "")
|
|
812
|
+
return f"OK: {data.get('lines', 0)} lines\n{content[:2000]}"
|
|
813
|
+
if tool_name == "list_files":
|
|
814
|
+
items = data.get("items", [])
|
|
815
|
+
names = [it["name"] if isinstance(it, dict) else str(it) for it in items[:20]]
|
|
816
|
+
return f"OK: {data.get('count', len(items))} items: {', '.join(names)}"
|
|
817
|
+
if tool_name == "search_code":
|
|
818
|
+
matches = data.get("matches", [])
|
|
819
|
+
return f"OK: {len(matches)} matches\n" + "\n".join(str(m)[:200] for m in matches[:10])
|
|
820
|
+
# Remote tools — JSON summary
|
|
821
|
+
return json.dumps(data, ensure_ascii=False)[:2000]
|
|
822
|
+
|
|
823
|
+
__all__ = ['_g', '_tool_analyze_file', '_tool_read_file', '_strip_markdown_fences', '_auto_fix_python', '_write_policy_confirm', '_tool_write_file', '_tool_edit_file', '_tool_multi_edit', '_tool_update_todos', '_tool_list_files', '_tool_search_code', '_tool_run_command', '_tool_web_fetch', '_tool_github', '_tool_glob', '_tool_notebook_read', '_tool_notebook_edit', '_tool_broker_query', '_tool_broker_order', '_tool_get_market_data', '_tool_get_market_history', '_todo_schema', '_wrap_bare_schemas', '_dedup_tool_schemas', '_show_edit_preview', '_show_multi_edit_preview', '_show_write_preview', '_apply_tool_approval', '_confirm_tool_execution_decision', 'execute_aria_tool', '_format_tool_summary', '_format_tool_summary_raw', 'truncate_tool_summary', 'DEFAULT_TOOL_RESULT_CHAR_LIMIT', 'MIN_TOOL_RESULT_CHAR_LIMIT']
|