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,598 @@
|
|
|
1
|
+
"""Run verifiable tasks and score them by exit code.
|
|
2
|
+
|
|
3
|
+
The shape of one task
|
|
4
|
+
---------------------
|
|
5
|
+
A fixture directory, a prompt, and a command::
|
|
6
|
+
|
|
7
|
+
- id: fix-failing-test
|
|
8
|
+
prompt: "tests/test_math.py 有一个测试失败了,修好它"
|
|
9
|
+
fixture: fix_failing_test
|
|
10
|
+
verify: "{python} -m pytest -q"
|
|
11
|
+
requires: [pytest]
|
|
12
|
+
|
|
13
|
+
The fixture is copied to a scratch directory, the agent is turned loose in the
|
|
14
|
+
copy, and ``verify`` decides the outcome. Exit 0 is a pass. Nothing else
|
|
15
|
+
counts — not a confident summary, not a diff that looks right.
|
|
16
|
+
|
|
17
|
+
The property that makes a suite trustworthy
|
|
18
|
+
-------------------------------------------
|
|
19
|
+
**Every task must start red.** Before the agent runs, the harness runs
|
|
20
|
+
``verify`` on the untouched fixture. If it already passes, the task is
|
|
21
|
+
reported ``INVALID`` and scored as neither pass nor fail.
|
|
22
|
+
|
|
23
|
+
This check is worth more than any individual task. A suite that silently
|
|
24
|
+
accumulates already-green tasks reports a rising pass rate while measuring
|
|
25
|
+
less and less, and the failure is invisible precisely because the number looks
|
|
26
|
+
good — you cannot tell a task the agent solved from a task that was never
|
|
27
|
+
broken. Running the check every time costs one command per task and makes the
|
|
28
|
+
number mean something.
|
|
29
|
+
|
|
30
|
+
"Red" has to mean the check ran
|
|
31
|
+
------------------------------
|
|
32
|
+
The pre-flight alone is not enough, and the first run of this harness proved
|
|
33
|
+
it: every task reported red, and every task was red because the ``python3`` on
|
|
34
|
+
PATH had no pytest. The suite looked healthy while measuring nothing at all —
|
|
35
|
+
the same silent-inflation failure the pre-flight exists to prevent, arriving
|
|
36
|
+
through the back door.
|
|
37
|
+
|
|
38
|
+
Two things close it. ``{python}`` in a command resolves to the interpreter
|
|
39
|
+
running the harness, so a suite verifies against the environment it was
|
|
40
|
+
launched with rather than whatever ``python3`` happens to mean on this
|
|
41
|
+
machine. And a task may declare ``requires: [pytest]``; a missing import is
|
|
42
|
+
reported as ``ERROR`` — excluded from the score — instead of being counted as
|
|
43
|
+
a red test the agent is expected to fix.
|
|
44
|
+
|
|
45
|
+
Isolation
|
|
46
|
+
---------
|
|
47
|
+
Tasks run in a copy under a scratch root, never in the fixture and never in
|
|
48
|
+
the repository. An agent that deletes the workspace, writes outside it, or
|
|
49
|
+
leaves it in a broken state affects nothing but its own run's directory, which
|
|
50
|
+
is what makes it safe to keep destructive tasks in the suite — those are the
|
|
51
|
+
ones worth having.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
from __future__ import annotations
|
|
55
|
+
|
|
56
|
+
import importlib.util
|
|
57
|
+
import json
|
|
58
|
+
import shutil
|
|
59
|
+
import subprocess
|
|
60
|
+
import sys
|
|
61
|
+
import tempfile
|
|
62
|
+
import time
|
|
63
|
+
from dataclasses import dataclass, field
|
|
64
|
+
from pathlib import Path
|
|
65
|
+
from typing import Any, Callable, Iterable, List, Optional, Sequence
|
|
66
|
+
|
|
67
|
+
__all__ = [
|
|
68
|
+
"SuiteResult",
|
|
69
|
+
"TaskResult",
|
|
70
|
+
"TaskSpec",
|
|
71
|
+
"Solver",
|
|
72
|
+
"load_suite",
|
|
73
|
+
"run_suite",
|
|
74
|
+
"run_task",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
# Outcomes. Kept as four values rather than a boolean because "the agent
|
|
78
|
+
# failed" and "the task was broken" call for opposite responses, and collapsing
|
|
79
|
+
# them is how a rotten suite goes unnoticed.
|
|
80
|
+
PASS = "pass"
|
|
81
|
+
FAIL = "fail"
|
|
82
|
+
INVALID = "invalid" # the fixture was already green: the task measures nothing
|
|
83
|
+
ERROR = "error" # the harness or the solver blew up; not the agent's score
|
|
84
|
+
|
|
85
|
+
_MAX_LOG_CHARS = 4000
|
|
86
|
+
|
|
87
|
+
# Residue that must not travel from a fixture into a task workspace.
|
|
88
|
+
# Directory names that hold build/run residue rather than source.
|
|
89
|
+
_RUN_RESIDUE = frozenset({
|
|
90
|
+
".git", "__pycache__", ".pytest_cache", ".mypy_cache", ".ruff_cache",
|
|
91
|
+
".tox", "node_modules", ".DS_Store",
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
_IGNORED_FIXTURE_ENTRIES = shutil.ignore_patterns(
|
|
95
|
+
"__pycache__", "*.pyc", "*.pyo", ".pytest_cache", ".mypy_cache",
|
|
96
|
+
".ruff_cache", ".git", "node_modules", ".DS_Store",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _trim(text: str, limit: int = _MAX_LOG_CHARS) -> str:
|
|
101
|
+
body = (text or "").strip()
|
|
102
|
+
if len(body) <= limit:
|
|
103
|
+
return body
|
|
104
|
+
half = limit // 2
|
|
105
|
+
return f"{body[:half]}\n… [truncated] …\n{body[-half:]}"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass(frozen=True)
|
|
109
|
+
class TaskSpec:
|
|
110
|
+
"""One verifiable task."""
|
|
111
|
+
|
|
112
|
+
id: str
|
|
113
|
+
prompt: str
|
|
114
|
+
verify: str
|
|
115
|
+
fixture: str = ""
|
|
116
|
+
timeout: int = 300
|
|
117
|
+
solve_timeout: int = 900
|
|
118
|
+
tags: tuple[str, ...] = ()
|
|
119
|
+
setup: tuple[str, ...] = ()
|
|
120
|
+
# Importable modules the check needs. Missing ones make the task ERROR
|
|
121
|
+
# rather than FAIL: an absent pytest is not a bug for the agent to fix.
|
|
122
|
+
requires: tuple[str, ...] = ()
|
|
123
|
+
# Files the agent must not modify. Defaults to the tests, because the
|
|
124
|
+
# check IS the tests: an agent that edits them can turn any task green
|
|
125
|
+
# without doing the work, and the score would be indistinguishable from a
|
|
126
|
+
# real solve. Editing one is scored FAIL no matter what the check says.
|
|
127
|
+
protect: tuple[str, ...] = ("test_*.py", "*_test.py", "tests/**")
|
|
128
|
+
# A task may legitimately start green when it is a regression guard: the
|
|
129
|
+
# point is that the agent must not *break* it. Opting out is explicit so
|
|
130
|
+
# that it is a decision someone made, not a fixture that quietly rotted.
|
|
131
|
+
allow_green_start: bool = False
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def from_dict(cls, data: dict) -> "TaskSpec":
|
|
135
|
+
missing = [key for key in ("id", "prompt", "verify") if not data.get(key)]
|
|
136
|
+
if missing:
|
|
137
|
+
raise ValueError(f"task is missing required field(s): {', '.join(missing)}")
|
|
138
|
+
return cls(
|
|
139
|
+
id=str(data["id"]),
|
|
140
|
+
prompt=str(data["prompt"]),
|
|
141
|
+
verify=str(data["verify"]),
|
|
142
|
+
fixture=str(data.get("fixture") or ""),
|
|
143
|
+
timeout=int(data.get("timeout") or 300),
|
|
144
|
+
solve_timeout=int(data.get("solve_timeout") or 900),
|
|
145
|
+
tags=tuple(str(t) for t in (data.get("tags") or ())),
|
|
146
|
+
setup=tuple(str(c) for c in (data.get("setup") or ())),
|
|
147
|
+
requires=tuple(str(m) for m in (data.get("requires") or ())),
|
|
148
|
+
protect=(
|
|
149
|
+
tuple(str(g) for g in data["protect"])
|
|
150
|
+
if "protect" in data else cls.protect
|
|
151
|
+
),
|
|
152
|
+
allow_green_start=bool(data.get("allow_green_start", False)),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@dataclass(frozen=True)
|
|
157
|
+
class TaskResult:
|
|
158
|
+
task_id: str
|
|
159
|
+
outcome: str
|
|
160
|
+
seconds: float = 0.0
|
|
161
|
+
exit_code: Optional[int] = None
|
|
162
|
+
detail: str = ""
|
|
163
|
+
log: str = ""
|
|
164
|
+
tags: tuple[str, ...] = ()
|
|
165
|
+
# What the agent actually touched. A red check with an empty list is a
|
|
166
|
+
# different failure from a red check after real edits: the first says the
|
|
167
|
+
# agent never engaged, the second says it engaged and got it wrong. Without
|
|
168
|
+
# this the two are indistinguishable in a report, and they call for
|
|
169
|
+
# opposite investigations.
|
|
170
|
+
changed: tuple[str, ...] = ()
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def counted(self) -> bool:
|
|
174
|
+
"""Whether this result belongs in the pass rate at all."""
|
|
175
|
+
return self.outcome in (PASS, FAIL)
|
|
176
|
+
|
|
177
|
+
def to_dict(self) -> dict:
|
|
178
|
+
return {
|
|
179
|
+
"task_id": self.task_id,
|
|
180
|
+
"outcome": self.outcome,
|
|
181
|
+
"seconds": round(self.seconds, 2),
|
|
182
|
+
"exit_code": self.exit_code,
|
|
183
|
+
"detail": self.detail,
|
|
184
|
+
"tags": list(self.tags),
|
|
185
|
+
"changed": list(self.changed),
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dataclass
|
|
190
|
+
class SuiteResult:
|
|
191
|
+
name: str
|
|
192
|
+
results: List[TaskResult] = field(default_factory=list)
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def passed(self) -> int:
|
|
196
|
+
return sum(1 for r in self.results if r.outcome == PASS)
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def failed(self) -> int:
|
|
200
|
+
return sum(1 for r in self.results if r.outcome == FAIL)
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def invalid(self) -> int:
|
|
204
|
+
return sum(1 for r in self.results if r.outcome == INVALID)
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def errored(self) -> int:
|
|
208
|
+
return sum(1 for r in self.results if r.outcome == ERROR)
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def scored(self) -> int:
|
|
212
|
+
return sum(1 for r in self.results if r.counted)
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def pass_rate(self) -> float:
|
|
216
|
+
"""Passes over *scored* tasks.
|
|
217
|
+
|
|
218
|
+
Invalid and errored tasks are excluded rather than counted as
|
|
219
|
+
failures. Counting them down would let one broken fixture look like an
|
|
220
|
+
agent regression; counting them up would let it hide one.
|
|
221
|
+
"""
|
|
222
|
+
return (self.passed / self.scored) if self.scored else 0.0
|
|
223
|
+
|
|
224
|
+
def by_tag(self) -> dict:
|
|
225
|
+
"""Pass rate per tag — the per-industry scoreboard."""
|
|
226
|
+
buckets: dict[str, list[TaskResult]] = {}
|
|
227
|
+
for result in self.results:
|
|
228
|
+
for tag in result.tags:
|
|
229
|
+
buckets.setdefault(tag, []).append(result)
|
|
230
|
+
out = {}
|
|
231
|
+
for tag, results in sorted(buckets.items()):
|
|
232
|
+
scored = [r for r in results if r.counted]
|
|
233
|
+
out[tag] = {
|
|
234
|
+
"passed": sum(1 for r in scored if r.outcome == PASS),
|
|
235
|
+
"scored": len(scored),
|
|
236
|
+
"pass_rate": round(
|
|
237
|
+
sum(1 for r in scored if r.outcome == PASS) / len(scored), 4
|
|
238
|
+
) if scored else 0.0,
|
|
239
|
+
}
|
|
240
|
+
return out
|
|
241
|
+
|
|
242
|
+
def to_dict(self) -> dict:
|
|
243
|
+
return {
|
|
244
|
+
"suite": self.name,
|
|
245
|
+
"pass_rate": round(self.pass_rate, 4),
|
|
246
|
+
"passed": self.passed,
|
|
247
|
+
"failed": self.failed,
|
|
248
|
+
"invalid": self.invalid,
|
|
249
|
+
"errored": self.errored,
|
|
250
|
+
"scored": self.scored,
|
|
251
|
+
"by_tag": self.by_tag(),
|
|
252
|
+
"per_task": self.per_task(),
|
|
253
|
+
"results": [r.to_dict() for r in self.results],
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
def merge(self, other: "SuiteResult") -> None:
|
|
257
|
+
"""Fold another run of the same suite into this one."""
|
|
258
|
+
self.results.extend(other.results)
|
|
259
|
+
|
|
260
|
+
def per_task(self) -> dict:
|
|
261
|
+
"""Passes over attempts for each task id, across every repeat.
|
|
262
|
+
|
|
263
|
+
A single sample is not a score. Observed on the first real run: one
|
|
264
|
+
task passed alone and failed in the same suite minutes later, from the
|
|
265
|
+
same model. Reporting 2/5 without saying how many attempts produced it
|
|
266
|
+
invites reading noise as a regression.
|
|
267
|
+
"""
|
|
268
|
+
buckets: dict[str, list[TaskResult]] = {}
|
|
269
|
+
for result in self.results:
|
|
270
|
+
buckets.setdefault(result.task_id, []).append(result)
|
|
271
|
+
out = {}
|
|
272
|
+
for task_id, results in buckets.items():
|
|
273
|
+
scored = [r for r in results if r.counted]
|
|
274
|
+
out[task_id] = {
|
|
275
|
+
"passed": sum(1 for r in scored if r.outcome == PASS),
|
|
276
|
+
"attempts": len(scored),
|
|
277
|
+
"outcomes": [r.outcome for r in results],
|
|
278
|
+
}
|
|
279
|
+
return out
|
|
280
|
+
|
|
281
|
+
def summary_line(self) -> str:
|
|
282
|
+
parts = [f"{self.name}: {self.passed}/{self.scored} ({self.pass_rate:.0%})"]
|
|
283
|
+
if self.invalid:
|
|
284
|
+
parts.append(f"{self.invalid} invalid")
|
|
285
|
+
if self.errored:
|
|
286
|
+
parts.append(f"{self.errored} error")
|
|
287
|
+
return " · ".join(parts)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# ``(prompt, workspace) -> anything``. Injected so the harness can be tested,
|
|
291
|
+
# and so the same suite can score a different agent — the CLI, a subagent
|
|
292
|
+
# backend, or a competitor — without the suite knowing which.
|
|
293
|
+
Solver = Callable[[str, Path], Any]
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _resolve(command: str) -> str:
|
|
297
|
+
"""Substitute ``{python}`` with the interpreter running the harness.
|
|
298
|
+
|
|
299
|
+
Without this a suite silently verifies against whatever ``python3`` means
|
|
300
|
+
on PATH, which is how five tasks once reported red because that
|
|
301
|
+
interpreter had no pytest installed.
|
|
302
|
+
"""
|
|
303
|
+
return (command or "").replace("{python}", sys.executable)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _missing_modules(names: Iterable[str]) -> list[str]:
|
|
307
|
+
missing = []
|
|
308
|
+
for name in names:
|
|
309
|
+
try:
|
|
310
|
+
if importlib.util.find_spec(name) is None:
|
|
311
|
+
missing.append(name)
|
|
312
|
+
except (ImportError, ValueError):
|
|
313
|
+
missing.append(name)
|
|
314
|
+
return missing
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _violates_protection(changed: Iterable[str], patterns: Iterable[str]) -> tuple[str, ...]:
|
|
318
|
+
"""Protected files the agent modified."""
|
|
319
|
+
import fnmatch
|
|
320
|
+
|
|
321
|
+
globs = tuple(patterns)
|
|
322
|
+
hits: list[str] = []
|
|
323
|
+
for rel in changed:
|
|
324
|
+
name = rel.replace("\\", "/")
|
|
325
|
+
base = name.rsplit("/", 1)[-1]
|
|
326
|
+
if any(fnmatch.fnmatch(name, g) or fnmatch.fnmatch(base, g) for g in globs):
|
|
327
|
+
hits.append(rel)
|
|
328
|
+
return tuple(hits)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _snapshot(root: Path) -> dict[str, str]:
|
|
332
|
+
"""Content digests for every file in *root*, keyed by relative path."""
|
|
333
|
+
import hashlib
|
|
334
|
+
|
|
335
|
+
out: dict[str, str] = {}
|
|
336
|
+
for path in sorted(root.rglob("*")):
|
|
337
|
+
if not path.is_file():
|
|
338
|
+
continue
|
|
339
|
+
rel = str(path.relative_to(root))
|
|
340
|
+
# Residue the *check* writes, not the agent's work. Reporting it makes
|
|
341
|
+
# every task look like it touched a dozen files and buries the one edit
|
|
342
|
+
# that matters.
|
|
343
|
+
if any(part in _RUN_RESIDUE for part in rel.replace("\\", "/").split("/")):
|
|
344
|
+
continue
|
|
345
|
+
try:
|
|
346
|
+
out[rel] = hashlib.sha256(path.read_bytes()).hexdigest()
|
|
347
|
+
except OSError:
|
|
348
|
+
continue
|
|
349
|
+
return out
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _changed_paths(before: dict[str, str], after: dict[str, str]) -> tuple[str, ...]:
|
|
353
|
+
"""Files the agent created, edited, or deleted."""
|
|
354
|
+
touched = [
|
|
355
|
+
rel for rel in sorted(set(before) | set(after))
|
|
356
|
+
if before.get(rel) != after.get(rel)
|
|
357
|
+
]
|
|
358
|
+
return tuple(touched)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _solver_failure(outcome: Any) -> Optional[tuple[int, str]]:
|
|
362
|
+
"""``(exit_code, log)`` when the solver reports it did not complete.
|
|
363
|
+
|
|
364
|
+
Duck-typed on ``returncode`` so a plain callable that returns nothing —
|
|
365
|
+
every in-process solver, and the check-only one — is unaffected, while a
|
|
366
|
+
``subprocess.CompletedProcess`` from the real agent is inspected.
|
|
367
|
+
"""
|
|
368
|
+
if outcome is None:
|
|
369
|
+
return None
|
|
370
|
+
code = getattr(outcome, "returncode", None)
|
|
371
|
+
if not isinstance(code, int) or code == 0:
|
|
372
|
+
return None
|
|
373
|
+
log = "\n".join(
|
|
374
|
+
part for part in (
|
|
375
|
+
str(getattr(outcome, "stderr", "") or ""),
|
|
376
|
+
str(getattr(outcome, "stdout", "") or ""),
|
|
377
|
+
) if part.strip()
|
|
378
|
+
)
|
|
379
|
+
return code, log
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _run(command: str, cwd: Path, timeout: int) -> tuple[int, str]:
|
|
383
|
+
command = _resolve(command)
|
|
384
|
+
try:
|
|
385
|
+
proc = subprocess.run(
|
|
386
|
+
command,
|
|
387
|
+
shell=True,
|
|
388
|
+
cwd=str(cwd),
|
|
389
|
+
capture_output=True,
|
|
390
|
+
text=True,
|
|
391
|
+
timeout=timeout,
|
|
392
|
+
check=False,
|
|
393
|
+
)
|
|
394
|
+
except subprocess.TimeoutExpired:
|
|
395
|
+
# A check that never finishes is a failed check. Reporting it as
|
|
396
|
+
# anything else would let an infinite loop score as a pass.
|
|
397
|
+
return 124, f"verification timed out after {timeout}s"
|
|
398
|
+
except Exception as exc:
|
|
399
|
+
return 125, f"could not run command: {exc}"
|
|
400
|
+
output = "\n".join(part for part in (proc.stdout, proc.stderr) if part.strip())
|
|
401
|
+
return proc.returncode, output
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def load_suite(path: str | Path) -> tuple[str, list[TaskSpec]]:
|
|
405
|
+
"""Read a YAML suite file into task specs."""
|
|
406
|
+
import yaml
|
|
407
|
+
|
|
408
|
+
file_path = Path(path).expanduser().resolve()
|
|
409
|
+
data = yaml.safe_load(file_path.read_text(encoding="utf-8")) or {}
|
|
410
|
+
name = str(data.get("suite") or file_path.stem)
|
|
411
|
+
tasks = [TaskSpec.from_dict(entry) for entry in (data.get("tasks") or [])]
|
|
412
|
+
|
|
413
|
+
duplicates = {t.id for t in tasks if [x.id for x in tasks].count(t.id) > 1}
|
|
414
|
+
if duplicates:
|
|
415
|
+
raise ValueError(f"duplicate task id(s) in {file_path.name}: {', '.join(sorted(duplicates))}")
|
|
416
|
+
return name, tasks
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _prepare_workspace(task: TaskSpec, fixtures_root: Path, scratch: Path) -> Path:
|
|
420
|
+
workspace = scratch / task.id
|
|
421
|
+
if workspace.exists():
|
|
422
|
+
shutil.rmtree(workspace)
|
|
423
|
+
if task.fixture:
|
|
424
|
+
source = fixtures_root / task.fixture
|
|
425
|
+
if not source.is_dir():
|
|
426
|
+
raise FileNotFoundError(f"fixture not found: {source}")
|
|
427
|
+
# Never copy build residue. A __pycache__ carried over from a run of
|
|
428
|
+
# the fixture in place holds bytecode for the *broken* source, and
|
|
429
|
+
# Python will happily import it — so a task could score red after a
|
|
430
|
+
# correct fix, or green before one.
|
|
431
|
+
shutil.copytree(source, workspace, ignore=_IGNORED_FIXTURE_ENTRIES)
|
|
432
|
+
else:
|
|
433
|
+
workspace.mkdir(parents=True)
|
|
434
|
+
return workspace
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def run_task(
|
|
438
|
+
task: TaskSpec,
|
|
439
|
+
*,
|
|
440
|
+
solver: Solver,
|
|
441
|
+
fixtures_root: str | Path,
|
|
442
|
+
scratch_root: str | Path | None = None,
|
|
443
|
+
keep_workspace: bool = False,
|
|
444
|
+
) -> TaskResult:
|
|
445
|
+
"""Prepare, pre-check, solve, and score one task."""
|
|
446
|
+
started = time.time()
|
|
447
|
+
fixtures = Path(fixtures_root).expanduser().resolve()
|
|
448
|
+
scratch = Path(scratch_root).expanduser().resolve() if scratch_root else Path(
|
|
449
|
+
tempfile.mkdtemp(prefix="aria-eval-")
|
|
450
|
+
)
|
|
451
|
+
scratch.mkdir(parents=True, exist_ok=True)
|
|
452
|
+
|
|
453
|
+
def _result(outcome: str, **kwargs) -> TaskResult:
|
|
454
|
+
return TaskResult(
|
|
455
|
+
task_id=task.id, outcome=outcome,
|
|
456
|
+
seconds=time.time() - started, tags=task.tags, **kwargs,
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
# Everything below runs inside this try so the scratch directory is removed
|
|
460
|
+
# on EVERY path, not just the happy one.
|
|
461
|
+
#
|
|
462
|
+
# It used to open after the setup checks, and the two error returns above
|
|
463
|
+
# it — a missing fixture, a missing dependency — leaked one temp directory
|
|
464
|
+
# each. That is once per error path per run, and the test suite exercises
|
|
465
|
+
# both deliberately, so a full test run leaked two directories every time.
|
|
466
|
+
# 57 of them had accumulated in $TMPDIR before anyone noticed, which is the
|
|
467
|
+
# shape of this class of bug: too small to be felt, never self-correcting.
|
|
468
|
+
try:
|
|
469
|
+
try:
|
|
470
|
+
workspace = _prepare_workspace(task, fixtures, scratch)
|
|
471
|
+
except Exception as exc:
|
|
472
|
+
return _result(ERROR, detail=f"workspace setup failed: {exc}")
|
|
473
|
+
|
|
474
|
+
missing = _missing_modules(task.requires)
|
|
475
|
+
if missing:
|
|
476
|
+
return _result(
|
|
477
|
+
ERROR,
|
|
478
|
+
detail=f"environment is missing {', '.join(missing)} — the check cannot run",
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
for command in task.setup:
|
|
482
|
+
code, output = _run(command, workspace, task.timeout)
|
|
483
|
+
if code != 0:
|
|
484
|
+
return _result(ERROR, exit_code=code,
|
|
485
|
+
detail=f"setup command failed: {command}",
|
|
486
|
+
log=_trim(output))
|
|
487
|
+
|
|
488
|
+
# ── the pre-flight ────────────────────────────────────────────────
|
|
489
|
+
# Confirm the task is actually broken before asking anyone to fix it.
|
|
490
|
+
before_code, before_log = _run(task.verify, workspace, task.timeout)
|
|
491
|
+
if before_code == 0 and not task.allow_green_start:
|
|
492
|
+
return _result(
|
|
493
|
+
INVALID, exit_code=0,
|
|
494
|
+
detail="fixture already passes its own check — this task measures nothing",
|
|
495
|
+
log=_trim(before_log),
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
# ── the agent's turn ──────────────────────────────────────────────
|
|
499
|
+
before = _snapshot(workspace)
|
|
500
|
+
try:
|
|
501
|
+
outcome = solver(task.prompt, workspace)
|
|
502
|
+
except Exception as exc:
|
|
503
|
+
# A solver crash is not the agent failing the task; scoring it as
|
|
504
|
+
# a fail would blame the model for a harness or provider outage.
|
|
505
|
+
return _result(ERROR, detail=f"solver raised: {exc}")
|
|
506
|
+
|
|
507
|
+
# ── the score ─────────────────────────────────────────────────────
|
|
508
|
+
# The check runs no matter what the solver reported, because the check
|
|
509
|
+
# is the source of truth and the solver's exit code is not. A run that
|
|
510
|
+
# edited the file correctly and then exited 1 on an empty final message
|
|
511
|
+
# is a PASS: the work is on disk and the tests are green.
|
|
512
|
+
changed = _changed_paths(before, _snapshot(workspace))
|
|
513
|
+
|
|
514
|
+
# Checked before the verdict, and it overrides a green check. Observed
|
|
515
|
+
# in a real run: an agent edited test_settlement.py rather than the
|
|
516
|
+
# module under test. The suite would have called that a PASS and the
|
|
517
|
+
# number would have been a lie.
|
|
518
|
+
tampered = _violates_protection(changed, task.protect)
|
|
519
|
+
if tampered:
|
|
520
|
+
return _result(
|
|
521
|
+
FAIL,
|
|
522
|
+
detail=f"modified protected file(s): {', '.join(tampered)}",
|
|
523
|
+
changed=changed,
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
after_code, after_log = _run(task.verify, workspace, task.timeout)
|
|
527
|
+
if after_code == 0:
|
|
528
|
+
return _result(PASS, exit_code=0, changed=changed)
|
|
529
|
+
|
|
530
|
+
# Red — now the solver's status decides who is to blame. A subprocess
|
|
531
|
+
# that died in seconds on a provider outage or a crash never gave the
|
|
532
|
+
# agent its turn, and scoring that as FAIL makes an infrastructure
|
|
533
|
+
# problem look like an incapable model. That is exactly the confusion
|
|
534
|
+
# this harness separates PASS/FAIL from ERROR to avoid.
|
|
535
|
+
failure = _solver_failure(outcome)
|
|
536
|
+
if failure is not None:
|
|
537
|
+
code, log = failure
|
|
538
|
+
return _result(
|
|
539
|
+
ERROR, exit_code=code,
|
|
540
|
+
detail=f"the agent did not complete (exit {code}); check still red",
|
|
541
|
+
log=_trim(log), changed=changed,
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
detail = f"`{task.verify}` exited {after_code}"
|
|
545
|
+
if not changed:
|
|
546
|
+
detail += " — the agent changed nothing"
|
|
547
|
+
return _result(
|
|
548
|
+
FAIL, exit_code=after_code, detail=detail,
|
|
549
|
+
log=_trim(after_log), changed=changed,
|
|
550
|
+
)
|
|
551
|
+
finally:
|
|
552
|
+
if not keep_workspace and scratch_root is None:
|
|
553
|
+
shutil.rmtree(scratch, ignore_errors=True)
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def run_suite(
|
|
557
|
+
tasks: Sequence[TaskSpec],
|
|
558
|
+
*,
|
|
559
|
+
solver: Solver,
|
|
560
|
+
fixtures_root: str | Path,
|
|
561
|
+
name: str = "suite",
|
|
562
|
+
scratch_root: str | Path | None = None,
|
|
563
|
+
only: Iterable[str] = (),
|
|
564
|
+
tags: Iterable[str] = (),
|
|
565
|
+
on_result: Optional[Callable[[TaskResult], None]] = None,
|
|
566
|
+
keep_workspace: bool = False,
|
|
567
|
+
) -> SuiteResult:
|
|
568
|
+
"""Run every task and collect the scoreboard."""
|
|
569
|
+
wanted_ids = {str(i) for i in only if i}
|
|
570
|
+
wanted_tags = {str(t) for t in tags if t}
|
|
571
|
+
|
|
572
|
+
selected = [
|
|
573
|
+
task for task in tasks
|
|
574
|
+
if (not wanted_ids or task.id in wanted_ids)
|
|
575
|
+
and (not wanted_tags or wanted_tags & set(task.tags))
|
|
576
|
+
]
|
|
577
|
+
|
|
578
|
+
suite = SuiteResult(name=name)
|
|
579
|
+
for task in selected:
|
|
580
|
+
result = run_task(
|
|
581
|
+
task,
|
|
582
|
+
solver=solver,
|
|
583
|
+
fixtures_root=fixtures_root,
|
|
584
|
+
scratch_root=scratch_root,
|
|
585
|
+
keep_workspace=keep_workspace,
|
|
586
|
+
)
|
|
587
|
+
suite.results.append(result)
|
|
588
|
+
if on_result is not None:
|
|
589
|
+
on_result(result)
|
|
590
|
+
return suite
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def write_report(suite: SuiteResult, path: str | Path) -> Path:
|
|
594
|
+
"""Persist the scoreboard as JSON so runs can be compared over time."""
|
|
595
|
+
out = Path(path).expanduser().resolve()
|
|
596
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
597
|
+
out.write_text(json.dumps(suite.to_dict(), ensure_ascii=False, indent=2), encoding="utf-8")
|
|
598
|
+
return out
|