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,90 @@
|
|
|
1
|
+
"""Deterministic task graphs for visible, verifiable Agent execution.
|
|
2
|
+
|
|
3
|
+
This is deliberately a planner rather than another LLM. The primary model
|
|
4
|
+
still reasons about the work, while the runtime supplies a stable contract:
|
|
5
|
+
what can run in parallel, what must be verified, and when a result is too weak
|
|
6
|
+
to be presented as complete.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Iterable
|
|
13
|
+
|
|
14
|
+
from aria_code.apps.cli.finance_service_catalog import classify_finance_market
|
|
15
|
+
from aria_code.apps.cli.intent_router import build_intent_route
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class TaskStage:
|
|
20
|
+
key: str
|
|
21
|
+
title: str
|
|
22
|
+
objective: str
|
|
23
|
+
mode: str = "read-only"
|
|
24
|
+
depends_on: tuple[str, ...] = ()
|
|
25
|
+
verification: str = ""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class TaskGraph:
|
|
30
|
+
request: str
|
|
31
|
+
kind: str
|
|
32
|
+
stages: tuple[TaskStage, ...]
|
|
33
|
+
|
|
34
|
+
def ready(self, completed: Iterable[str]) -> tuple[TaskStage, ...]:
|
|
35
|
+
done = set(completed)
|
|
36
|
+
return tuple(stage for stage in self.stages if stage.key not in done and set(stage.depends_on) <= done)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def build_task_graph(request: str) -> TaskGraph:
|
|
40
|
+
"""Build an auditable graph from the user's request without network I/O."""
|
|
41
|
+
text = str(request or "").strip()
|
|
42
|
+
route = build_intent_route(text)
|
|
43
|
+
market = classify_finance_market(text)
|
|
44
|
+
financial_intents = {
|
|
45
|
+
"market_snapshot", "market_analysis", "market_research", "report", "chart",
|
|
46
|
+
"backtest", "strategy", "ashare", "hk_market", "us_market", "crypto",
|
|
47
|
+
"forex", "commodity",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if route.allows_code_autorun or route.primary == "code":
|
|
51
|
+
return TaskGraph(text, "engineering", (
|
|
52
|
+
TaskStage("inspect", "范围与现状", "读取相关文件、约束和现有测试,确认最小改动范围。", verification="列出将修改的文件和不触碰的用户改动。"),
|
|
53
|
+
TaskStage("implement", "实现", "在隔离工作区完成最小可验证实现。", mode="workspace-write", depends_on=("inspect",)),
|
|
54
|
+
TaskStage("verify", "验证", "运行相关测试、静态检查和必要的手动冒烟检查。", depends_on=("implement",), verification="失败必须保留错误和未验证项。"),
|
|
55
|
+
TaskStage("review", "审查与交付", "审查 diff、风险和验证证据,再总结交付结果。", depends_on=("verify",), verification="未经验证的改动不得标记为完成。"),
|
|
56
|
+
))
|
|
57
|
+
|
|
58
|
+
if market is not None or any(intent in financial_intents for intent in route.intents):
|
|
59
|
+
market_label = market.label if market else "目标市场"
|
|
60
|
+
stages = [
|
|
61
|
+
TaskStage(
|
|
62
|
+
"evidence", "数据与事实", f"取得 {market_label} 的行情、时间戳、来源和可用覆盖范围。",
|
|
63
|
+
verification="缺失、过期或来源失败必须显式标记,不能补造数值。",
|
|
64
|
+
),
|
|
65
|
+
TaskStage(
|
|
66
|
+
"research", "新闻与基本面", "核验与任务相关的公告、财报、新闻或宏观事件;区分事实和推断。",
|
|
67
|
+
depends_on=("evidence",), verification="每个关键结论需要来源、日期和影响路径。",
|
|
68
|
+
),
|
|
69
|
+
TaskStage(
|
|
70
|
+
"analysis", "分析与情景", "基于证据输出趋势、风险、正反情景和失效条件。",
|
|
71
|
+
depends_on=("evidence", "research"), verification="不得把技术指标、新闻情绪或预测概率表述为确定事实。",
|
|
72
|
+
),
|
|
73
|
+
]
|
|
74
|
+
if market and market.key == "CN" and any(word in text.lower() for word in ("预测", "明天", "明日", "次日", "涨", "跌")):
|
|
75
|
+
stages.append(TaskStage(
|
|
76
|
+
"prediction", "量化预测校验", "调用 A 股次交易日引擎,并记录标的覆盖率与样本外评估口径。",
|
|
77
|
+
depends_on=("evidence",), verification="必须展示数据日期、覆盖率、回测/评估质量和非覆盖标的。",
|
|
78
|
+
))
|
|
79
|
+
stages.append(TaskStage(
|
|
80
|
+
"quality_gate", "交付前验证", "检查事实、数据日期、矛盾结论和风险披露。",
|
|
81
|
+
depends_on=tuple(stage.key for stage in stages if stage.key != "quality_gate"),
|
|
82
|
+
verification="任何关键数据不合格时降级为资料不足,不输出投资指令。",
|
|
83
|
+
))
|
|
84
|
+
return TaskGraph(text, "financial_research", tuple(stages))
|
|
85
|
+
|
|
86
|
+
return TaskGraph(text, "general", (
|
|
87
|
+
TaskStage("clarify", "目标与约束", "提取目标、已知信息、边界和缺失前提。"),
|
|
88
|
+
TaskStage("execute", "执行", "按目标完成研究、分析或操作。", depends_on=("clarify",)),
|
|
89
|
+
TaskStage("verify", "结果核验", "核对结果是否直接回答目标,并披露不确定性。", depends_on=("execute",)),
|
|
90
|
+
))
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""Tool execution layer for Aria Code runtimes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Awaitable, Callable, Dict, Mapping, Optional
|
|
9
|
+
|
|
10
|
+
from .approval import ApprovalDecision, apply_approval_decision
|
|
11
|
+
from .events import RuntimeTrace, ToolCallRecord
|
|
12
|
+
|
|
13
|
+
ToolHandler = Callable[[dict], dict]
|
|
14
|
+
RemoteExecutor = Callable[[str, dict], Awaitable[dict]]
|
|
15
|
+
Hook = Callable[[str, str, dict, Optional[dict]], None]
|
|
16
|
+
ExecutionContextProvider = Callable[[], Mapping[str, Any]]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ToolExecutor:
|
|
20
|
+
"""Execute local/remote tools with hooks, policy injection, and trace records."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
local_tools: Mapping[str, tuple],
|
|
25
|
+
*,
|
|
26
|
+
remote_executor: RemoteExecutor | None = None,
|
|
27
|
+
hook: Hook | None = None,
|
|
28
|
+
trace: RuntimeTrace | None = None,
|
|
29
|
+
config: Dict[str, Any] | None = None,
|
|
30
|
+
execution_context: ExecutionContextProvider | None = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
self.local_tools = local_tools
|
|
33
|
+
self.remote_executor = remote_executor
|
|
34
|
+
self.hook = hook
|
|
35
|
+
self.trace = trace or RuntimeTrace()
|
|
36
|
+
self.config = config or {}
|
|
37
|
+
self.execution_context = execution_context
|
|
38
|
+
|
|
39
|
+
def execute_local(
|
|
40
|
+
self, tool_name: str, params: dict, *, approval: ApprovalDecision | None = None
|
|
41
|
+
) -> dict:
|
|
42
|
+
"""Execute a local tool synchronously."""
|
|
43
|
+
if tool_name not in self.local_tools:
|
|
44
|
+
return {"success": False, "error": f"Unknown local tool: {tool_name}"}
|
|
45
|
+
handler = self.local_tools[tool_name][0]
|
|
46
|
+
params = self._prepare_params(
|
|
47
|
+
tool_name,
|
|
48
|
+
params,
|
|
49
|
+
include_execution_context=True,
|
|
50
|
+
bind_research_context=True,
|
|
51
|
+
approval=approval,
|
|
52
|
+
)
|
|
53
|
+
context_error = params.pop("_execution_context_error", None)
|
|
54
|
+
if context_error:
|
|
55
|
+
result = {"success": False, "error": context_error}
|
|
56
|
+
self.trace.emit("tool_denied", {"tool": tool_name, "reason": context_error})
|
|
57
|
+
return result
|
|
58
|
+
return self._call_with_trace(tool_name, params, lambda: handler(params))
|
|
59
|
+
|
|
60
|
+
async def execute(self, tool_name: str, params: dict) -> dict:
|
|
61
|
+
"""Execute a tool asynchronously, using remote executor when needed."""
|
|
62
|
+
if tool_name in self.local_tools:
|
|
63
|
+
loop = asyncio.get_event_loop()
|
|
64
|
+
return await loop.run_in_executor(None, self.execute_local, tool_name, params)
|
|
65
|
+
if self.remote_executor is None:
|
|
66
|
+
return {"success": False, "error": f"Unknown tool: {tool_name}"}
|
|
67
|
+
params = self._prepare_params(tool_name, params, bind_research_context=True)
|
|
68
|
+
start = time.time()
|
|
69
|
+
self._run_hook("pre_tool", tool_name, params)
|
|
70
|
+
self.trace.emit("tool_call", {"tool": tool_name, "params": params})
|
|
71
|
+
try:
|
|
72
|
+
result = await self.remote_executor(tool_name, params)
|
|
73
|
+
except Exception as exc:
|
|
74
|
+
result = {"success": False, "error": str(exc)}
|
|
75
|
+
self._run_hook("post_tool", tool_name, params, result)
|
|
76
|
+
end = time.time()
|
|
77
|
+
self.trace.add_tool_call(ToolCallRecord(
|
|
78
|
+
tool=tool_name,
|
|
79
|
+
params=params,
|
|
80
|
+
result=result,
|
|
81
|
+
elapsed_ms=(end - start) * 1000,
|
|
82
|
+
started_at=start,
|
|
83
|
+
ended_at=end,
|
|
84
|
+
))
|
|
85
|
+
return result
|
|
86
|
+
|
|
87
|
+
def _call_with_trace(self, tool_name: str, params: dict, fn: Callable[[], dict]) -> dict:
|
|
88
|
+
start = time.time()
|
|
89
|
+
self._run_hook("pre_tool", tool_name, params)
|
|
90
|
+
self.trace.emit("tool_call", {"tool": tool_name, "params": params})
|
|
91
|
+
try:
|
|
92
|
+
result = fn()
|
|
93
|
+
except Exception as exc:
|
|
94
|
+
result = {"success": False, "error": str(exc)}
|
|
95
|
+
self._run_hook("post_tool", tool_name, params, result)
|
|
96
|
+
end = time.time()
|
|
97
|
+
self.trace.add_tool_call(ToolCallRecord(
|
|
98
|
+
tool=tool_name,
|
|
99
|
+
params=params,
|
|
100
|
+
result=result,
|
|
101
|
+
elapsed_ms=(end - start) * 1000,
|
|
102
|
+
started_at=start,
|
|
103
|
+
ended_at=end,
|
|
104
|
+
))
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
def _prepare_params(
|
|
108
|
+
self,
|
|
109
|
+
tool_name: str,
|
|
110
|
+
params: dict,
|
|
111
|
+
*,
|
|
112
|
+
include_execution_context: bool = False,
|
|
113
|
+
bind_research_context: bool = False,
|
|
114
|
+
approval: ApprovalDecision | None = None,
|
|
115
|
+
) -> dict:
|
|
116
|
+
prepared = dict(params or {})
|
|
117
|
+
if tool_name == "run_command":
|
|
118
|
+
# Tool arguments come from the model. Only the host configuration and
|
|
119
|
+
# a typed approval decision may set execution controls.
|
|
120
|
+
for key in (
|
|
121
|
+
"policy", "permission_mode", "network_enabled",
|
|
122
|
+
"user_approved", "_upgrade_policy", "sandbox",
|
|
123
|
+
):
|
|
124
|
+
prepared.pop(key, None)
|
|
125
|
+
if self.execution_context is not None and (
|
|
126
|
+
include_execution_context or bind_research_context
|
|
127
|
+
):
|
|
128
|
+
try:
|
|
129
|
+
context = dict(self.execution_context() or {})
|
|
130
|
+
if include_execution_context:
|
|
131
|
+
for key, value in context.items():
|
|
132
|
+
if str(key).startswith("_") and value is not None:
|
|
133
|
+
prepared[str(key)] = value
|
|
134
|
+
self._bind_workspace(tool_name, prepared, context)
|
|
135
|
+
if bind_research_context:
|
|
136
|
+
self._bind_research_run(tool_name, prepared, context)
|
|
137
|
+
except Exception as exc:
|
|
138
|
+
if include_execution_context:
|
|
139
|
+
prepared["_execution_context_error"] = (
|
|
140
|
+
f"Execution context unavailable: {exc}"
|
|
141
|
+
)
|
|
142
|
+
if tool_name == "run_command":
|
|
143
|
+
prepared["policy"] = self.config.get("command_policy", "safe")
|
|
144
|
+
prepared["permission_mode"] = self.config.get("permission_mode", "workspace-write")
|
|
145
|
+
prepared["network_enabled"] = bool(self.config.get("network_enabled", True))
|
|
146
|
+
prepared["sandbox"] = bool(self.config.get("command_sandbox", False))
|
|
147
|
+
if approval is not None and approval.approved:
|
|
148
|
+
apply_approval_decision(prepared, approval)
|
|
149
|
+
return prepared
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def _bind_research_run(
|
|
153
|
+
tool_name: str,
|
|
154
|
+
prepared: dict,
|
|
155
|
+
context: Mapping[str, Any],
|
|
156
|
+
) -> None:
|
|
157
|
+
"""Correlate an institutional research run with its upstream Aria run."""
|
|
158
|
+
canonical_name = str(tool_name).rsplit("__", 1)[-1]
|
|
159
|
+
if canonical_name != "research_run_create":
|
|
160
|
+
return
|
|
161
|
+
control_run_id = context.get("_run_id")
|
|
162
|
+
trace_id = context.get("_trace_id") or control_run_id
|
|
163
|
+
if control_run_id:
|
|
164
|
+
prepared.setdefault("control_run_id", str(control_run_id))
|
|
165
|
+
if trace_id:
|
|
166
|
+
prepared.setdefault("trace_id", str(trace_id))
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def _bind_workspace(tool_name: str, prepared: dict, context: Mapping[str, Any]) -> None:
|
|
170
|
+
workspace_value = context.get("_workspace")
|
|
171
|
+
if not workspace_value:
|
|
172
|
+
return
|
|
173
|
+
workspace = Path(str(workspace_value)).expanduser().resolve()
|
|
174
|
+
restricted = bool(context.get("_workspace_restricted", False))
|
|
175
|
+
path_tools = {
|
|
176
|
+
"read_file",
|
|
177
|
+
"write_file",
|
|
178
|
+
"edit_file",
|
|
179
|
+
"multi_edit",
|
|
180
|
+
"list_files",
|
|
181
|
+
"search_code",
|
|
182
|
+
"analyze_file",
|
|
183
|
+
"notebook_read",
|
|
184
|
+
"notebook_edit",
|
|
185
|
+
}
|
|
186
|
+
if tool_name in path_tools:
|
|
187
|
+
raw_path = str(prepared.get("path") or ".")
|
|
188
|
+
target = Path(raw_path).expanduser()
|
|
189
|
+
if not target.is_absolute():
|
|
190
|
+
target = workspace / target
|
|
191
|
+
target = target.resolve()
|
|
192
|
+
if restricted and not target.is_relative_to(workspace):
|
|
193
|
+
prepared["_execution_context_error"] = (
|
|
194
|
+
f"Tool path is outside the isolated workspace: {target}"
|
|
195
|
+
)
|
|
196
|
+
return
|
|
197
|
+
prepared["path"] = str(target)
|
|
198
|
+
if tool_name in {"run_command", "github"}:
|
|
199
|
+
raw_cwd = str(prepared.get("cwd") or workspace)
|
|
200
|
+
cwd = Path(raw_cwd).expanduser()
|
|
201
|
+
if not cwd.is_absolute():
|
|
202
|
+
cwd = workspace / cwd
|
|
203
|
+
cwd = cwd.resolve()
|
|
204
|
+
if restricted and not cwd.is_relative_to(workspace):
|
|
205
|
+
prepared["_execution_context_error"] = (
|
|
206
|
+
f"Command cwd is outside the isolated workspace: {cwd}"
|
|
207
|
+
)
|
|
208
|
+
return
|
|
209
|
+
prepared["cwd"] = str(cwd)
|
|
210
|
+
|
|
211
|
+
def _run_hook(self, hook_type: str, tool_name: str, params: dict, result: dict | None = None) -> None:
|
|
212
|
+
if self.hook is None:
|
|
213
|
+
return
|
|
214
|
+
try:
|
|
215
|
+
self.hook(hook_type, tool_name, params, result)
|
|
216
|
+
except Exception:
|
|
217
|
+
pass
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Persistent per-tool execution policy: allowlist, denylist, ask-always.
|
|
2
|
+
|
|
3
|
+
Stores policy in ~/.arthera/tool_policy.json.
|
|
4
|
+
Checked in _confirm_tool_execution_decision() before any user prompt.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from runtime.tool_policy import check_tool_policy, add_to_policy
|
|
8
|
+
|
|
9
|
+
verdict = check_tool_policy("write_file") # "allow" | "deny" | "ask" | "default"
|
|
10
|
+
add_to_policy("read_file", "allow") # permanently auto-approve
|
|
11
|
+
add_to_policy("run_command", "deny") # permanently block
|
|
12
|
+
add_to_policy("edit_file", "ask") # always prompt
|
|
13
|
+
remove_from_policy("read_file") # remove from all lists
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Literal
|
|
21
|
+
from aria_code.packages.aria_core.paths import aria_home
|
|
22
|
+
|
|
23
|
+
PolicyVerdict = Literal["allow", "deny", "ask", "default"]
|
|
24
|
+
|
|
25
|
+
_DEFAULT_POLICY: dict = {
|
|
26
|
+
"allowed": [], # auto-approve without prompt
|
|
27
|
+
"denied": [], # always block, never execute
|
|
28
|
+
"ask_always": [], # always prompt even for non-CONFIRM_TOOLS
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _policy_file() -> Path:
|
|
33
|
+
return aria_home() / "tool_policy.json"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def load_tool_policy() -> dict:
|
|
37
|
+
"""Load policy from disk; returns defaults if missing or corrupt."""
|
|
38
|
+
f = _policy_file()
|
|
39
|
+
if f.exists():
|
|
40
|
+
try:
|
|
41
|
+
raw = json.loads(f.read_text(encoding="utf-8"))
|
|
42
|
+
return {
|
|
43
|
+
"allowed": list(raw.get("allowed", [])),
|
|
44
|
+
"denied": list(raw.get("denied", [])),
|
|
45
|
+
"ask_always": list(raw.get("ask_always", [])),
|
|
46
|
+
}
|
|
47
|
+
except Exception:
|
|
48
|
+
pass
|
|
49
|
+
return {k: list(v) for k, v in _DEFAULT_POLICY.items()}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def save_tool_policy(policy: dict) -> None:
|
|
53
|
+
f = _policy_file()
|
|
54
|
+
f.parent.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
f.write_text(json.dumps(policy, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def check_tool_policy(tool_name: str) -> PolicyVerdict:
|
|
59
|
+
"""Return the persistent verdict for *tool_name*.
|
|
60
|
+
|
|
61
|
+
"allow" → auto-approve, skip confirmation prompt
|
|
62
|
+
"deny" → block, never execute
|
|
63
|
+
"ask" → always prompt (overrides session auto-allow)
|
|
64
|
+
"default" → no override, fall through to normal flow
|
|
65
|
+
"""
|
|
66
|
+
policy = load_tool_policy()
|
|
67
|
+
if tool_name in policy.get("denied", []):
|
|
68
|
+
return "deny"
|
|
69
|
+
if tool_name in policy.get("allowed", []):
|
|
70
|
+
return "allow"
|
|
71
|
+
if tool_name in policy.get("ask_always", []):
|
|
72
|
+
return "ask"
|
|
73
|
+
return "default"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def add_to_policy(tool_name: str, verdict: Literal["allow", "deny", "ask"]) -> None:
|
|
77
|
+
"""Add *tool_name* to the given policy list, removing it from any other list first."""
|
|
78
|
+
policy = load_tool_policy()
|
|
79
|
+
for key in ("allowed", "denied", "ask_always"):
|
|
80
|
+
policy.setdefault(key, [])
|
|
81
|
+
if tool_name in policy[key]:
|
|
82
|
+
policy[key].remove(tool_name)
|
|
83
|
+
if verdict == "allow":
|
|
84
|
+
policy["allowed"].append(tool_name)
|
|
85
|
+
elif verdict == "deny":
|
|
86
|
+
policy["denied"].append(tool_name)
|
|
87
|
+
elif verdict == "ask":
|
|
88
|
+
policy["ask_always"].append(tool_name)
|
|
89
|
+
save_tool_policy(policy)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def remove_from_policy(tool_name: str) -> bool:
|
|
93
|
+
"""Remove *tool_name* from all lists. Returns True if it was present."""
|
|
94
|
+
policy = load_tool_policy()
|
|
95
|
+
changed = False
|
|
96
|
+
for key in ("allowed", "denied", "ask_always"):
|
|
97
|
+
if tool_name in policy.get(key, []):
|
|
98
|
+
policy[key].remove(tool_name)
|
|
99
|
+
changed = True
|
|
100
|
+
if changed:
|
|
101
|
+
save_tool_policy(policy)
|
|
102
|
+
return changed
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def policy_summary() -> dict:
|
|
106
|
+
"""Return current policy as a human-readable dict."""
|
|
107
|
+
return load_tool_policy()
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Git worktree isolation for write-capable background agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class WorktreeError(RuntimeError):
|
|
12
|
+
"""Raised when an isolated worktree cannot be created safely."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class WorktreeSpec:
|
|
17
|
+
task_id: str
|
|
18
|
+
repository: str
|
|
19
|
+
path: str
|
|
20
|
+
branch: str
|
|
21
|
+
base_revision: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class WorktreeApplyResult:
|
|
26
|
+
paths: tuple[str, ...]
|
|
27
|
+
tracked_paths: tuple[str, ...]
|
|
28
|
+
untracked_paths: tuple[str, ...]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class WorktreeManager:
|
|
32
|
+
"""Create task-specific worktrees without changing the caller's cwd."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, root: Path | str) -> None:
|
|
35
|
+
self.root = Path(root).expanduser().resolve()
|
|
36
|
+
self.root.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def _git(*args: str, cwd: Path) -> str:
|
|
40
|
+
try:
|
|
41
|
+
completed = subprocess.run(
|
|
42
|
+
["git", *args],
|
|
43
|
+
cwd=str(cwd),
|
|
44
|
+
text=True,
|
|
45
|
+
capture_output=True,
|
|
46
|
+
timeout=30,
|
|
47
|
+
check=False,
|
|
48
|
+
)
|
|
49
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
50
|
+
raise WorktreeError(f"Unable to run git: {exc}") from exc
|
|
51
|
+
if completed.returncode != 0:
|
|
52
|
+
detail = (completed.stderr or completed.stdout).strip()
|
|
53
|
+
raise WorktreeError(detail or f"git {' '.join(args)} failed")
|
|
54
|
+
return completed.stdout.strip()
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def repository_root(cls, workspace: Path | str) -> Path:
|
|
58
|
+
path = Path(workspace).expanduser().resolve()
|
|
59
|
+
output = cls._git("rev-parse", "--show-toplevel", cwd=path)
|
|
60
|
+
return Path(output).resolve()
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def is_clean(cls, repository: Path | str) -> bool:
|
|
64
|
+
repo = Path(repository).expanduser().resolve()
|
|
65
|
+
return not cls._git("status", "--porcelain", "--untracked-files=all", cwd=repo)
|
|
66
|
+
|
|
67
|
+
def create(
|
|
68
|
+
self,
|
|
69
|
+
*,
|
|
70
|
+
task_id: str,
|
|
71
|
+
workspace: Path | str,
|
|
72
|
+
require_clean: bool = True,
|
|
73
|
+
) -> WorktreeSpec:
|
|
74
|
+
repository = self.repository_root(workspace)
|
|
75
|
+
if require_clean and not self.is_clean(repository):
|
|
76
|
+
raise WorktreeError(
|
|
77
|
+
"The repository has uncommitted changes. Commit or stash them before "
|
|
78
|
+
"starting a write-capable isolated subagent."
|
|
79
|
+
)
|
|
80
|
+
base_revision = self._git("rev-parse", "HEAD", cwd=repository)
|
|
81
|
+
branch = f"aria/task-{task_id}"
|
|
82
|
+
destination = (self.root / repository.name / task_id).resolve()
|
|
83
|
+
if destination.exists():
|
|
84
|
+
raise WorktreeError(f"Worktree path already exists: {destination}")
|
|
85
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
try:
|
|
87
|
+
self._git(
|
|
88
|
+
"worktree",
|
|
89
|
+
"add",
|
|
90
|
+
"-b",
|
|
91
|
+
branch,
|
|
92
|
+
str(destination),
|
|
93
|
+
base_revision,
|
|
94
|
+
cwd=repository,
|
|
95
|
+
)
|
|
96
|
+
except Exception:
|
|
97
|
+
if destination.exists():
|
|
98
|
+
shutil.rmtree(destination, ignore_errors=True)
|
|
99
|
+
raise
|
|
100
|
+
return WorktreeSpec(
|
|
101
|
+
task_id=task_id,
|
|
102
|
+
repository=str(repository),
|
|
103
|
+
path=str(destination),
|
|
104
|
+
branch=branch,
|
|
105
|
+
base_revision=base_revision,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def remove(self, spec: WorktreeSpec, *, force: bool = False) -> None:
|
|
109
|
+
repository = Path(spec.repository)
|
|
110
|
+
path = Path(spec.path)
|
|
111
|
+
args = ["worktree", "remove"]
|
|
112
|
+
if force:
|
|
113
|
+
args.append("--force")
|
|
114
|
+
args.append(str(path))
|
|
115
|
+
self._git(*args, cwd=repository)
|
|
116
|
+
|
|
117
|
+
def delete_branch(self, spec: WorktreeSpec, *, force: bool = False) -> None:
|
|
118
|
+
flag = "-D" if force else "-d"
|
|
119
|
+
self._git("branch", flag, spec.branch, cwd=Path(spec.repository))
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def diff(cls, spec: WorktreeSpec) -> str:
|
|
123
|
+
worktree = Path(spec.path)
|
|
124
|
+
diff_stat = cls._git("diff", "--stat", spec.base_revision, cwd=worktree)
|
|
125
|
+
untracked = cls._git("ls-files", "--others", "--exclude-standard", cwd=worktree)
|
|
126
|
+
parts = [part for part in (diff_stat, untracked) if part]
|
|
127
|
+
return "\n".join(parts)
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def _git_binary(*args: str, cwd: Path, input_data: bytes | None = None) -> bytes:
|
|
131
|
+
try:
|
|
132
|
+
completed = subprocess.run(
|
|
133
|
+
["git", *args],
|
|
134
|
+
cwd=str(cwd),
|
|
135
|
+
input=input_data,
|
|
136
|
+
capture_output=True,
|
|
137
|
+
timeout=30,
|
|
138
|
+
check=False,
|
|
139
|
+
)
|
|
140
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
141
|
+
raise WorktreeError(f"Unable to run git: {exc}") from exc
|
|
142
|
+
if completed.returncode != 0:
|
|
143
|
+
detail = (completed.stderr or completed.stdout).decode("utf-8", errors="replace").strip()
|
|
144
|
+
raise WorktreeError(detail or f"git {' '.join(args)} failed")
|
|
145
|
+
return completed.stdout
|
|
146
|
+
|
|
147
|
+
def apply(self, spec: WorktreeSpec) -> WorktreeApplyResult:
|
|
148
|
+
"""Apply an isolated worktree's unstaged result to a clean main worktree."""
|
|
149
|
+
repository = Path(spec.repository)
|
|
150
|
+
worktree = Path(spec.path)
|
|
151
|
+
if not self.is_clean(repository):
|
|
152
|
+
raise WorktreeError(
|
|
153
|
+
"The target workspace changed after the subagent started. "
|
|
154
|
+
"Commit or stash those changes before applying the task result."
|
|
155
|
+
)
|
|
156
|
+
target_revision = self._git("rev-parse", "HEAD", cwd=repository)
|
|
157
|
+
if target_revision != spec.base_revision:
|
|
158
|
+
raise WorktreeError(
|
|
159
|
+
"The target branch advanced after the subagent started. "
|
|
160
|
+
"Review or rebase the task worktree before applying it."
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
tracked, untracked = self.changed_paths(spec)
|
|
164
|
+
if not tracked and not untracked:
|
|
165
|
+
raise WorktreeError("The subagent worktree contains no changes to apply.")
|
|
166
|
+
|
|
167
|
+
for relative in untracked:
|
|
168
|
+
destination = repository / relative
|
|
169
|
+
if destination.exists():
|
|
170
|
+
raise WorktreeError(
|
|
171
|
+
f"Refusing to overwrite an existing untracked path: {destination}"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
patch = self._git_binary(
|
|
175
|
+
"diff", "--binary", spec.base_revision, cwd=worktree
|
|
176
|
+
)
|
|
177
|
+
if patch:
|
|
178
|
+
self._git_binary("apply", "--check", "-", cwd=repository, input_data=patch)
|
|
179
|
+
|
|
180
|
+
copied: list[Path] = []
|
|
181
|
+
patch_applied = False
|
|
182
|
+
try:
|
|
183
|
+
if patch:
|
|
184
|
+
self._git_binary("apply", "-", cwd=repository, input_data=patch)
|
|
185
|
+
patch_applied = True
|
|
186
|
+
for relative in untracked:
|
|
187
|
+
source = worktree / relative
|
|
188
|
+
destination = repository / relative
|
|
189
|
+
if source.is_symlink():
|
|
190
|
+
raise WorktreeError(f"Refusing to copy an untracked symlink: {source}")
|
|
191
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
192
|
+
shutil.copy2(source, destination)
|
|
193
|
+
copied.append(destination)
|
|
194
|
+
except Exception:
|
|
195
|
+
for destination in reversed(copied):
|
|
196
|
+
if destination.exists():
|
|
197
|
+
destination.unlink()
|
|
198
|
+
if patch_applied:
|
|
199
|
+
self._git_binary("apply", "--reverse", "-", cwd=repository, input_data=patch)
|
|
200
|
+
raise
|
|
201
|
+
|
|
202
|
+
paths = tuple(dict.fromkeys([*tracked, *untracked]))
|
|
203
|
+
return WorktreeApplyResult(paths, tracked, untracked)
|
|
204
|
+
|
|
205
|
+
def changed_paths(self, spec: WorktreeSpec) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
|
206
|
+
worktree = Path(spec.path)
|
|
207
|
+
tracked_text = self._git(
|
|
208
|
+
"diff", "--name-only", spec.base_revision, cwd=worktree
|
|
209
|
+
)
|
|
210
|
+
tracked = tuple(path for path in tracked_text.splitlines() if path)
|
|
211
|
+
untracked_text = self._git(
|
|
212
|
+
"ls-files", "--others", "--exclude-standard", cwd=worktree
|
|
213
|
+
)
|
|
214
|
+
untracked = tuple(path for path in untracked_text.splitlines() if path)
|
|
215
|
+
return tracked, untracked
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Safety and permission primitives for Aria Code."""
|
|
2
|
+
|
|
3
|
+
from .service import SafetyService
|
|
4
|
+
from .permissions import (
|
|
5
|
+
PermissionDecision,
|
|
6
|
+
PermissionMode,
|
|
7
|
+
PermissionService,
|
|
8
|
+
PolicyDecision,
|
|
9
|
+
classify_command_risk,
|
|
10
|
+
evaluate_command_policy,
|
|
11
|
+
normalize_command,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"SafetyService",
|
|
16
|
+
"PermissionDecision",
|
|
17
|
+
"PermissionMode",
|
|
18
|
+
"PermissionService",
|
|
19
|
+
"PolicyDecision",
|
|
20
|
+
"classify_command_risk",
|
|
21
|
+
"evaluate_command_policy",
|
|
22
|
+
"normalize_command",
|
|
23
|
+
]
|