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,547 @@
|
|
|
1
|
+
"""Background subagent task system.
|
|
2
|
+
|
|
3
|
+
Allows the main agent to spawn independent sub-tasks that run concurrently.
|
|
4
|
+
Tasks are tracked in memory; code checkpoints and applied worktree changes are
|
|
5
|
+
persisted by the shared runtime store.
|
|
6
|
+
|
|
7
|
+
Tool functions exposed to the LLM:
|
|
8
|
+
spawn_task(prompt, context?) → {"task_id": "abc123", "status": "pending"}
|
|
9
|
+
task_status(task_id) → {"status": "running|done|failed", ...}
|
|
10
|
+
task_result(task_id) → {"result": "...", "success": bool}
|
|
11
|
+
task_list() → [{"task_id": ..., "status": ...}, ...]
|
|
12
|
+
task_cancel(task_id) → {"cancelled": bool}
|
|
13
|
+
|
|
14
|
+
Background execution is wired up in aria_cli.py via _subagent_runner().
|
|
15
|
+
If no runner is registered, spawn_task stores the task for manual execution.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import asyncio
|
|
21
|
+
import inspect
|
|
22
|
+
import time
|
|
23
|
+
import uuid
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any, Callable, Dict, Optional
|
|
27
|
+
|
|
28
|
+
from .task_ledger import TaskLedger
|
|
29
|
+
|
|
30
|
+
_TASKS: Dict[str, "SubagentTask"] = {}
|
|
31
|
+
_RUNNER: Optional[Callable] = None # set by aria_cli.py
|
|
32
|
+
_LEDGER: Optional[TaskLedger] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class SubagentTask:
|
|
37
|
+
task_id: str
|
|
38
|
+
prompt: str
|
|
39
|
+
context: str = ""
|
|
40
|
+
status: str = "pending" # pending | running | done | failed | cancelled
|
|
41
|
+
result: str = ""
|
|
42
|
+
error: str = ""
|
|
43
|
+
mode: str = "read-only"
|
|
44
|
+
isolation: str = "shared"
|
|
45
|
+
backend: str = "aria" # aria | claude | codex — which agent actually runs the task
|
|
46
|
+
workspace: str = ""
|
|
47
|
+
session_id: str = ""
|
|
48
|
+
branch: str = ""
|
|
49
|
+
diff_summary: str = ""
|
|
50
|
+
applied: bool = False
|
|
51
|
+
applied_paths: tuple[str, ...] = ()
|
|
52
|
+
created_at: float = field(default_factory=time.time)
|
|
53
|
+
completed_at: float = 0.0
|
|
54
|
+
handoff: dict[str, Any] = field(default_factory=dict)
|
|
55
|
+
async_task: Optional[asyncio.Task] = field(default=None, repr=False, compare=False)
|
|
56
|
+
worktree_spec: Optional[Any] = field(default=None, repr=False, compare=False)
|
|
57
|
+
|
|
58
|
+
def age_str(self) -> str:
|
|
59
|
+
elapsed = time.time() - self.created_at
|
|
60
|
+
if elapsed < 60:
|
|
61
|
+
return f"{int(elapsed)}s"
|
|
62
|
+
if elapsed < 3600:
|
|
63
|
+
return f"{elapsed/60:.1f}m"
|
|
64
|
+
return f"{elapsed/3600:.1f}h"
|
|
65
|
+
|
|
66
|
+
def to_dict(self) -> dict:
|
|
67
|
+
return {
|
|
68
|
+
"task_id": self.task_id,
|
|
69
|
+
"status": self.status,
|
|
70
|
+
"prompt": self.prompt[:200] + ("…" if len(self.prompt) > 200 else ""),
|
|
71
|
+
"age": self.age_str(),
|
|
72
|
+
"mode": self.mode,
|
|
73
|
+
"isolation": self.isolation,
|
|
74
|
+
"backend": self.backend,
|
|
75
|
+
"workspace": self.workspace or None,
|
|
76
|
+
"branch": self.branch or None,
|
|
77
|
+
"applied": self.applied,
|
|
78
|
+
"handoff": self.handoff or None,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
def snapshot(self) -> dict[str, Any]:
|
|
82
|
+
"""Return the serializable task contract and latest outcome."""
|
|
83
|
+
return {
|
|
84
|
+
"task_id": self.task_id, "prompt": self.prompt, "context": self.context,
|
|
85
|
+
"status": self.status, "result": self.result, "error": self.error,
|
|
86
|
+
"mode": self.mode, "isolation": self.isolation, "backend": self.backend,
|
|
87
|
+
"workspace": self.workspace, "session_id": self.session_id,
|
|
88
|
+
"branch": self.branch, "diff_summary": self.diff_summary,
|
|
89
|
+
"applied": self.applied, "applied_paths": list(self.applied_paths),
|
|
90
|
+
"created_at": self.created_at, "completed_at": self.completed_at,
|
|
91
|
+
"handoff": self.handoff,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _ledger() -> TaskLedger:
|
|
96
|
+
global _LEDGER
|
|
97
|
+
if _LEDGER is None:
|
|
98
|
+
_LEDGER = TaskLedger()
|
|
99
|
+
return _LEDGER
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _persist(task: SubagentTask) -> None:
|
|
103
|
+
"""A ledger write must never make a live task fail."""
|
|
104
|
+
try:
|
|
105
|
+
_ledger().upsert(task.snapshot())
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def restore_tasks() -> int:
|
|
111
|
+
"""Restore prior task records after a CLI restart.
|
|
112
|
+
|
|
113
|
+
A running coroutine cannot safely be resumed, so it is retained as an
|
|
114
|
+
explicit interrupted task rather than silently disappearing.
|
|
115
|
+
"""
|
|
116
|
+
restored = 0
|
|
117
|
+
for record in _ledger().restore():
|
|
118
|
+
task_id = str(record.get("task_id") or "").strip()
|
|
119
|
+
if not task_id or task_id in _TASKS:
|
|
120
|
+
continue
|
|
121
|
+
status = str(record.get("status") or "pending")
|
|
122
|
+
error = str(record.get("error") or "")
|
|
123
|
+
if status == "running":
|
|
124
|
+
status = "interrupted"
|
|
125
|
+
error = "CLI restarted before this task completed; rerun or delegate it again."
|
|
126
|
+
task = SubagentTask(
|
|
127
|
+
task_id=task_id, prompt=str(record.get("prompt") or ""),
|
|
128
|
+
context=str(record.get("context") or ""), status=status,
|
|
129
|
+
result=str(record.get("result") or ""), error=error,
|
|
130
|
+
mode=str(record.get("mode") or "read-only"),
|
|
131
|
+
isolation=str(record.get("isolation") or "shared"),
|
|
132
|
+
backend=str(record.get("backend") or "aria"),
|
|
133
|
+
workspace=str(record.get("workspace") or ""),
|
|
134
|
+
session_id=str(record.get("session_id") or ""),
|
|
135
|
+
branch=str(record.get("branch") or ""),
|
|
136
|
+
diff_summary=str(record.get("diff_summary") or ""),
|
|
137
|
+
applied=bool(record.get("applied")),
|
|
138
|
+
applied_paths=tuple(record.get("applied_paths") or ()),
|
|
139
|
+
created_at=float(record.get("created_at") or time.time()),
|
|
140
|
+
completed_at=float(record.get("completed_at") or 0.0),
|
|
141
|
+
handoff=dict(record.get("handoff") or {}),
|
|
142
|
+
)
|
|
143
|
+
_TASKS[task_id] = task
|
|
144
|
+
_persist(task)
|
|
145
|
+
restored += 1
|
|
146
|
+
return restored
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def register_runner(runner: Callable) -> None:
|
|
150
|
+
"""Register the async runner function from aria_cli.py."""
|
|
151
|
+
global _RUNNER
|
|
152
|
+
_RUNNER = runner
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ── Tool functions ─────────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
def tool_spawn_task(params: dict) -> dict:
|
|
158
|
+
"""Spawn an independent background agent task."""
|
|
159
|
+
prompt = params.get("prompt", "")
|
|
160
|
+
context = params.get("context", "")
|
|
161
|
+
if not prompt:
|
|
162
|
+
return {"success": False, "error": "Missing 'prompt'"}
|
|
163
|
+
|
|
164
|
+
mode = str(params.get("mode") or "read-only").lower()
|
|
165
|
+
isolation = str(params.get("isolation") or "auto").lower()
|
|
166
|
+
backend = str(params.get("backend") or "aria").lower()
|
|
167
|
+
if mode not in {"read-only", "workspace-write"}:
|
|
168
|
+
return {"success": False, "error": "mode must be read-only or workspace-write"}
|
|
169
|
+
if isolation not in {"auto", "worktree", "shared"}:
|
|
170
|
+
return {"success": False, "error": "isolation must be auto, worktree, or shared"}
|
|
171
|
+
if backend not in {"aria", "claude", "codex"}:
|
|
172
|
+
return {"success": False, "error": "backend must be aria, claude, or codex"}
|
|
173
|
+
if isolation == "auto":
|
|
174
|
+
isolation = "worktree" if mode == "workspace-write" else "shared"
|
|
175
|
+
if mode == "read-only" and isolation == "worktree":
|
|
176
|
+
isolation = "shared"
|
|
177
|
+
if mode == "workspace-write" and isolation == "shared":
|
|
178
|
+
return {
|
|
179
|
+
"success": False,
|
|
180
|
+
"error": "workspace-write tasks require worktree isolation",
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
task_id = uuid.uuid4().hex[:8]
|
|
184
|
+
task = SubagentTask(
|
|
185
|
+
task_id=task_id,
|
|
186
|
+
prompt=prompt,
|
|
187
|
+
context=context,
|
|
188
|
+
mode=mode,
|
|
189
|
+
isolation=isolation,
|
|
190
|
+
backend=backend,
|
|
191
|
+
workspace=str(params.get("_workspace") or params.get("_spawn_workspace") or Path.cwd()),
|
|
192
|
+
session_id=str(params.get("_session_id") or ""),
|
|
193
|
+
)
|
|
194
|
+
_TASKS[task_id] = task
|
|
195
|
+
_persist(task)
|
|
196
|
+
|
|
197
|
+
# An external backend (claude/codex) always has a runner (the CLI itself);
|
|
198
|
+
# the "aria" backend needs one registered via register_runner().
|
|
199
|
+
if backend != "aria" or _RUNNER is not None:
|
|
200
|
+
try:
|
|
201
|
+
loop = asyncio.get_event_loop()
|
|
202
|
+
if loop.is_running():
|
|
203
|
+
task.async_task = asyncio.ensure_future(_run_background(task))
|
|
204
|
+
else:
|
|
205
|
+
loop.run_until_complete(_run_background(task))
|
|
206
|
+
except Exception as exc:
|
|
207
|
+
task.status = "failed"
|
|
208
|
+
task.error = str(exc)
|
|
209
|
+
task.completed_at = time.time()
|
|
210
|
+
_persist(task)
|
|
211
|
+
else:
|
|
212
|
+
# No runner — task stays in "pending" for manual execution
|
|
213
|
+
task.status = "pending"
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
"success": True,
|
|
217
|
+
"task_id": task_id,
|
|
218
|
+
"status": task.status,
|
|
219
|
+
"mode": task.mode,
|
|
220
|
+
"isolation": task.isolation,
|
|
221
|
+
"message": (
|
|
222
|
+
f"Task {task_id} spawned. Use task_status('{task_id}') to check progress."
|
|
223
|
+
if task.status == "running" else
|
|
224
|
+
f"Task {task_id} queued (no runner registered). Check /tasks."
|
|
225
|
+
),
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
async def _run_background(task: SubagentTask) -> None:
|
|
230
|
+
task.status = "running"
|
|
231
|
+
_persist(task)
|
|
232
|
+
try:
|
|
233
|
+
if task.mode == "workspace-write" and task.isolation == "worktree":
|
|
234
|
+
from apps.cli.config_paths import resolve_config_dir
|
|
235
|
+
from .worktrees import WorktreeManager
|
|
236
|
+
|
|
237
|
+
manager = WorktreeManager(resolve_config_dir() / "worktrees")
|
|
238
|
+
spec = manager.create(task_id=task.task_id, workspace=task.workspace)
|
|
239
|
+
task.worktree_spec = spec
|
|
240
|
+
task.workspace = spec.path
|
|
241
|
+
task.branch = spec.branch
|
|
242
|
+
|
|
243
|
+
full_prompt = task.prompt
|
|
244
|
+
if task.context:
|
|
245
|
+
full_prompt = f"{task.context}\n\n{task.prompt}"
|
|
246
|
+
execution_contract = (
|
|
247
|
+
"[Subagent execution contract]\n"
|
|
248
|
+
f"Mode: {task.mode}\n"
|
|
249
|
+
f"Workspace: {task.workspace}\n"
|
|
250
|
+
f"Isolation: {task.isolation}\n"
|
|
251
|
+
"Keep every file and command operation inside this workspace. "
|
|
252
|
+
"Do not spawn another subagent or push changes. End with a structured "
|
|
253
|
+
"handoff: FINDINGS: ...; CHANGED_FILES: ...; VERIFICATION: ...; BLOCKERS: ..."
|
|
254
|
+
)
|
|
255
|
+
full_prompt = f"{execution_contract}\n\n{full_prompt}"
|
|
256
|
+
if task.backend != "aria":
|
|
257
|
+
from external_agent_runner import RUNNERS
|
|
258
|
+
|
|
259
|
+
result_text = await RUNNERS[task.backend](full_prompt, cwd=task.workspace or None)
|
|
260
|
+
else:
|
|
261
|
+
runner_parameters = inspect.signature(_RUNNER).parameters
|
|
262
|
+
if len(runner_parameters) >= 2:
|
|
263
|
+
result_text = await _RUNNER(full_prompt, task)
|
|
264
|
+
else:
|
|
265
|
+
result_text = await _RUNNER(full_prompt)
|
|
266
|
+
task.result = result_text or ""
|
|
267
|
+
task.handoff = _extract_handoff(task.result)
|
|
268
|
+
task.status = "done"
|
|
269
|
+
if task.worktree_spec is not None:
|
|
270
|
+
from .worktrees import WorktreeManager
|
|
271
|
+
task.diff_summary = WorktreeManager.diff(task.worktree_spec)
|
|
272
|
+
except asyncio.CancelledError:
|
|
273
|
+
task.status = "cancelled"
|
|
274
|
+
except Exception as exc:
|
|
275
|
+
task.error = str(exc)
|
|
276
|
+
task.status = "failed"
|
|
277
|
+
finally:
|
|
278
|
+
task.completed_at = time.time()
|
|
279
|
+
_persist(task)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _extract_handoff(result: str) -> dict[str, str]:
|
|
283
|
+
"""Extract a compact handoff when the agent supplied the task contract."""
|
|
284
|
+
text = str(result or "")
|
|
285
|
+
keys = ("FINDINGS", "CHANGED_FILES", "VERIFICATION", "BLOCKERS")
|
|
286
|
+
out: dict[str, str] = {}
|
|
287
|
+
for index, key in enumerate(keys):
|
|
288
|
+
marker = f"{key}:"
|
|
289
|
+
start = text.rfind(marker)
|
|
290
|
+
if start < 0:
|
|
291
|
+
continue
|
|
292
|
+
end = len(text)
|
|
293
|
+
for later_key in keys[index + 1:]:
|
|
294
|
+
candidate = text.find(f"{later_key}:", start + len(marker))
|
|
295
|
+
if candidate >= 0:
|
|
296
|
+
end = min(end, candidate)
|
|
297
|
+
out[key.lower()] = text[start + len(marker):end].strip().strip(";")
|
|
298
|
+
return out
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def tool_task_status(params: dict) -> dict:
|
|
302
|
+
"""Check the status of a background task."""
|
|
303
|
+
task_id = params.get("task_id", "")
|
|
304
|
+
if not task_id:
|
|
305
|
+
return {"success": False, "error": "Missing 'task_id'"}
|
|
306
|
+
task = _TASKS.get(task_id)
|
|
307
|
+
if not task:
|
|
308
|
+
return {"success": False, "error": f"Task '{task_id}' not found"}
|
|
309
|
+
return {
|
|
310
|
+
"success": True,
|
|
311
|
+
"task_id": task_id,
|
|
312
|
+
"status": task.status,
|
|
313
|
+
"age": task.age_str(),
|
|
314
|
+
"prompt_preview": task.prompt[:100],
|
|
315
|
+
"error": task.error or None,
|
|
316
|
+
"mode": task.mode,
|
|
317
|
+
"isolation": task.isolation,
|
|
318
|
+
"workspace": task.workspace or None,
|
|
319
|
+
"branch": task.branch or None,
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def tool_task_result(params: dict) -> dict:
|
|
324
|
+
"""Retrieve the result of a completed background task."""
|
|
325
|
+
task_id = params.get("task_id", "")
|
|
326
|
+
if not task_id:
|
|
327
|
+
return {"success": False, "error": "Missing 'task_id'"}
|
|
328
|
+
task = _TASKS.get(task_id)
|
|
329
|
+
if not task:
|
|
330
|
+
return {"success": False, "error": f"Task '{task_id}' not found"}
|
|
331
|
+
if task.status == "running":
|
|
332
|
+
return {"success": False, "error": "Task is still running", "status": "running"}
|
|
333
|
+
if task.status == "failed":
|
|
334
|
+
return {"success": False, "error": task.error, "status": "failed"}
|
|
335
|
+
if task.status in ("pending", "cancelled", "interrupted"):
|
|
336
|
+
return {"success": False, "error": f"Task status is '{task.status}'", "status": task.status}
|
|
337
|
+
return {
|
|
338
|
+
"success": True,
|
|
339
|
+
"task_id": task_id,
|
|
340
|
+
"status": task.status,
|
|
341
|
+
"result": task.result,
|
|
342
|
+
"age": task.age_str(),
|
|
343
|
+
"workspace": task.workspace or None,
|
|
344
|
+
"branch": task.branch or None,
|
|
345
|
+
"diff_summary": task.diff_summary or None,
|
|
346
|
+
"applied": task.applied,
|
|
347
|
+
"applied_paths": list(task.applied_paths),
|
|
348
|
+
"handoff": task.handoff or None,
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def tool_task_list(params: dict) -> dict:
|
|
353
|
+
"""List all tracked background tasks."""
|
|
354
|
+
tasks = [t.to_dict() for t in _TASKS.values()]
|
|
355
|
+
if not tasks:
|
|
356
|
+
return {"success": True, "tasks": [], "message": "No active tasks."}
|
|
357
|
+
by_status: dict = {}
|
|
358
|
+
for t in tasks:
|
|
359
|
+
s = t["status"]
|
|
360
|
+
by_status.setdefault(s, []).append(t)
|
|
361
|
+
return {
|
|
362
|
+
"success": True,
|
|
363
|
+
"total": len(tasks),
|
|
364
|
+
"tasks": tasks,
|
|
365
|
+
"summary": {s: len(v) for s, v in by_status.items()},
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def tool_task_cancel(params: dict) -> dict:
|
|
370
|
+
"""Cancel a pending or running background task."""
|
|
371
|
+
task_id = params.get("task_id", "")
|
|
372
|
+
if not task_id:
|
|
373
|
+
return {"success": False, "error": "Missing 'task_id'"}
|
|
374
|
+
task = _TASKS.get(task_id)
|
|
375
|
+
if not task:
|
|
376
|
+
return {"success": False, "error": f"Task '{task_id}' not found"}
|
|
377
|
+
if task.status in ("done", "failed", "cancelled"):
|
|
378
|
+
return {"success": False, "error": f"Task already in terminal state: {task.status}"}
|
|
379
|
+
task.status = "cancelled"
|
|
380
|
+
task.completed_at = time.time()
|
|
381
|
+
if task.async_task is not None and not task.async_task.done():
|
|
382
|
+
task.async_task.cancel()
|
|
383
|
+
_persist(task)
|
|
384
|
+
return {"success": True, "task_id": task_id, "cancelled": True}
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def apply_task_worktree(task_id: str, *, cleanup: bool = True) -> dict:
|
|
388
|
+
"""Apply a completed isolated task to the original clean workspace."""
|
|
389
|
+
task = _TASKS.get(task_id)
|
|
390
|
+
if not task:
|
|
391
|
+
return {"success": False, "error": f"Task '{task_id}' not found"}
|
|
392
|
+
if task.status != "done":
|
|
393
|
+
return {"success": False, "error": f"Task status is '{task.status}', expected 'done'"}
|
|
394
|
+
if task.applied:
|
|
395
|
+
return {"success": False, "error": "Task changes were already applied"}
|
|
396
|
+
if task.worktree_spec is None:
|
|
397
|
+
return {"success": False, "error": "Task has no isolated worktree changes"}
|
|
398
|
+
try:
|
|
399
|
+
from .worktrees import WorktreeManager
|
|
400
|
+
from apps.cli.config_paths import resolve_config_dir
|
|
401
|
+
|
|
402
|
+
manager = WorktreeManager(resolve_config_dir() / "worktrees")
|
|
403
|
+
tracked, untracked = manager.changed_paths(task.worktree_spec)
|
|
404
|
+
target_root = Path(task.worktree_spec.repository)
|
|
405
|
+
snapshots = {}
|
|
406
|
+
for relative in (*tracked, *untracked):
|
|
407
|
+
target = target_root / relative
|
|
408
|
+
if target.is_file():
|
|
409
|
+
try:
|
|
410
|
+
content = target.read_text(encoding="utf-8")
|
|
411
|
+
except UnicodeDecodeError:
|
|
412
|
+
continue
|
|
413
|
+
snapshots[relative] = (
|
|
414
|
+
True,
|
|
415
|
+
content,
|
|
416
|
+
target.stat().st_mode & 0o777,
|
|
417
|
+
)
|
|
418
|
+
elif not target.exists():
|
|
419
|
+
snapshots[relative] = (False, "", None)
|
|
420
|
+
result = manager.apply(task.worktree_spec)
|
|
421
|
+
checkpoint_errors = []
|
|
422
|
+
try:
|
|
423
|
+
from .checkpoints import CheckpointStore
|
|
424
|
+
checkpoint_store = CheckpointStore()
|
|
425
|
+
for relative in result.paths:
|
|
426
|
+
target = target_root / relative
|
|
427
|
+
before = snapshots.get(relative)
|
|
428
|
+
if before is None or not target.is_file():
|
|
429
|
+
continue
|
|
430
|
+
try:
|
|
431
|
+
after_content = target.read_text(encoding="utf-8")
|
|
432
|
+
except UnicodeDecodeError:
|
|
433
|
+
continue
|
|
434
|
+
checkpoint_store.record_change(
|
|
435
|
+
path=target,
|
|
436
|
+
before_content=before[1],
|
|
437
|
+
after_content=after_content,
|
|
438
|
+
existed_before=before[0],
|
|
439
|
+
before_mode=before[2],
|
|
440
|
+
source="subagent_apply",
|
|
441
|
+
session_id=task.session_id,
|
|
442
|
+
label=f"task {task.task_id}: {relative}",
|
|
443
|
+
metadata={"task_id": task.task_id, "branch": task.branch},
|
|
444
|
+
)
|
|
445
|
+
except Exception as exc:
|
|
446
|
+
checkpoint_errors.append(str(exc))
|
|
447
|
+
task.applied = True
|
|
448
|
+
task.applied_paths = result.paths
|
|
449
|
+
_persist(task)
|
|
450
|
+
cleanup_error = ""
|
|
451
|
+
if cleanup:
|
|
452
|
+
try:
|
|
453
|
+
manager.remove(task.worktree_spec, force=True)
|
|
454
|
+
manager.delete_branch(task.worktree_spec, force=True)
|
|
455
|
+
except Exception as exc:
|
|
456
|
+
cleanup_error = str(exc)
|
|
457
|
+
return {
|
|
458
|
+
"success": True,
|
|
459
|
+
"task_id": task_id,
|
|
460
|
+
"paths": list(result.paths),
|
|
461
|
+
"cleanup_error": cleanup_error or None,
|
|
462
|
+
"checkpoint_errors": checkpoint_errors,
|
|
463
|
+
}
|
|
464
|
+
except Exception as exc:
|
|
465
|
+
return {"success": False, "error": str(exc)}
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
# ── Tool registry (added to LOCAL_TOOLS in aria_cli.py) ───────────────────────
|
|
469
|
+
|
|
470
|
+
SUBAGENT_TOOLS = {
|
|
471
|
+
"spawn_task": (tool_spawn_task, "Spawn a background sub-task; returns task_id"),
|
|
472
|
+
"task_status": (tool_task_status, "Check status of a background task by task_id"),
|
|
473
|
+
"task_result": (tool_task_result, "Retrieve result of a completed background task"),
|
|
474
|
+
"task_list": (tool_task_list, "List all active background tasks"),
|
|
475
|
+
"task_cancel": (tool_task_cancel, "Cancel a pending or running background task"),
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
SUBAGENT_SCHEMAS = [
|
|
479
|
+
{
|
|
480
|
+
"name": "spawn_task",
|
|
481
|
+
"description": "Spawn an independent background agent task. Useful for parallelising slow operations: research, data fetching, long analysis. Returns a task_id you can poll with task_status.",
|
|
482
|
+
"parameters": {
|
|
483
|
+
"type": "object",
|
|
484
|
+
"properties": {
|
|
485
|
+
"prompt": {"type": "string", "description": "The task for the sub-agent to perform"},
|
|
486
|
+
"context": {"type": "string", "description": "Optional background context to inject into the sub-agent's system prompt"},
|
|
487
|
+
"mode": {
|
|
488
|
+
"type": "string",
|
|
489
|
+
"enum": ["read-only", "workspace-write"],
|
|
490
|
+
"description": "Use workspace-write only when the subagent must edit code. Default: read-only.",
|
|
491
|
+
},
|
|
492
|
+
"isolation": {
|
|
493
|
+
"type": "string",
|
|
494
|
+
"enum": ["auto", "worktree", "shared"],
|
|
495
|
+
"description": "auto uses a Git worktree for write tasks; shared is only valid for read-only tasks.",
|
|
496
|
+
},
|
|
497
|
+
"backend": {
|
|
498
|
+
"type": "string",
|
|
499
|
+
"enum": ["aria", "claude", "codex"],
|
|
500
|
+
"description": "Which agent actually runs the task. 'aria' (default) uses this session's own loop. 'claude'/'codex' shell out to the Claude Code or Codex CLI (must be installed and on PATH) headlessly — use for coding tasks you want a different coding agent to own.",
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
"required": ["prompt"],
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
"name": "task_status",
|
|
508
|
+
"description": "Check the status of a background task spawned with spawn_task.",
|
|
509
|
+
"parameters": {
|
|
510
|
+
"type": "object",
|
|
511
|
+
"properties": {
|
|
512
|
+
"task_id": {"type": "string", "description": "The task_id returned by spawn_task"},
|
|
513
|
+
},
|
|
514
|
+
"required": ["task_id"],
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
"name": "task_result",
|
|
519
|
+
"description": "Retrieve the full result text of a completed background task.",
|
|
520
|
+
"parameters": {
|
|
521
|
+
"type": "object",
|
|
522
|
+
"properties": {
|
|
523
|
+
"task_id": {"type": "string", "description": "The task_id returned by spawn_task"},
|
|
524
|
+
},
|
|
525
|
+
"required": ["task_id"],
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
"name": "task_list",
|
|
530
|
+
"description": "List all active and completed background tasks with their statuses.",
|
|
531
|
+
"parameters": {
|
|
532
|
+
"type": "object",
|
|
533
|
+
"properties": {},
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
"name": "task_cancel",
|
|
538
|
+
"description": "Cancel a pending or running background task.",
|
|
539
|
+
"parameters": {
|
|
540
|
+
"type": "object",
|
|
541
|
+
"properties": {
|
|
542
|
+
"task_id": {"type": "string", "description": "The task_id to cancel"},
|
|
543
|
+
},
|
|
544
|
+
"required": ["task_id"],
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Durable, local task ledger for background agents.
|
|
2
|
+
|
|
3
|
+
The live scheduler intentionally remains in-memory, but an agent task must not
|
|
4
|
+
disappear merely because the CLI is restarted. This small JSON ledger records
|
|
5
|
+
the user-visible task contract, state, handoff and evidence. It contains no
|
|
6
|
+
credentials and is safe to inspect locally.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import tempfile
|
|
15
|
+
import time
|
|
16
|
+
from typing import Any, Iterable, Mapping
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
LEDGER_VERSION = 1
|
|
20
|
+
MAX_RESULT_CHARS = 32_000
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def default_ledger_path() -> Path:
|
|
24
|
+
override = os.getenv("ARIA_TASK_LEDGER_PATH", "").strip()
|
|
25
|
+
if override:
|
|
26
|
+
return Path(override).expanduser()
|
|
27
|
+
root = Path(os.getenv("ARIA_CONFIG_DIR") or (Path.home() / ".aria"))
|
|
28
|
+
return root / "task_ledger.json"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TaskLedger:
|
|
32
|
+
"""Atomic JSON persistence for task snapshots.
|
|
33
|
+
|
|
34
|
+
The ledger stores a mapping rather than an append-only log because callers
|
|
35
|
+
need fast status restoration after a restart. ``updated_at`` remains an
|
|
36
|
+
auditable ordering signal.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, path: Path | str | None = None) -> None:
|
|
40
|
+
self.path = Path(path or default_ledger_path()).expanduser()
|
|
41
|
+
|
|
42
|
+
def load(self) -> dict[str, dict[str, Any]]:
|
|
43
|
+
try:
|
|
44
|
+
payload = json.loads(self.path.read_text(encoding="utf-8"))
|
|
45
|
+
records = payload.get("tasks", {}) if isinstance(payload, Mapping) else {}
|
|
46
|
+
return {
|
|
47
|
+
str(task_id): dict(record)
|
|
48
|
+
for task_id, record in records.items()
|
|
49
|
+
if isinstance(record, Mapping)
|
|
50
|
+
}
|
|
51
|
+
except (OSError, ValueError, TypeError):
|
|
52
|
+
return {}
|
|
53
|
+
|
|
54
|
+
def save(self, records: Mapping[str, Mapping[str, Any]]) -> None:
|
|
55
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
56
|
+
payload = {
|
|
57
|
+
"version": LEDGER_VERSION,
|
|
58
|
+
"updated_at": time.time(),
|
|
59
|
+
"tasks": records,
|
|
60
|
+
}
|
|
61
|
+
encoded = json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True)
|
|
62
|
+
fd, temp_name = tempfile.mkstemp(prefix=".task_ledger_", dir=self.path.parent)
|
|
63
|
+
try:
|
|
64
|
+
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
65
|
+
handle.write(encoded)
|
|
66
|
+
os.replace(temp_name, self.path)
|
|
67
|
+
finally:
|
|
68
|
+
if os.path.exists(temp_name):
|
|
69
|
+
os.unlink(temp_name)
|
|
70
|
+
|
|
71
|
+
def upsert(self, record: Mapping[str, Any]) -> None:
|
|
72
|
+
task_id = str(record.get("task_id") or "").strip()
|
|
73
|
+
if not task_id:
|
|
74
|
+
raise ValueError("Task record requires task_id")
|
|
75
|
+
records = self.load()
|
|
76
|
+
snapshot = dict(record)
|
|
77
|
+
snapshot["updated_at"] = time.time()
|
|
78
|
+
result = str(snapshot.get("result") or "")
|
|
79
|
+
if len(result) > MAX_RESULT_CHARS:
|
|
80
|
+
snapshot["result"] = result[:MAX_RESULT_CHARS] + "\n…[truncated in task ledger]"
|
|
81
|
+
snapshot["result_truncated"] = True
|
|
82
|
+
records[task_id] = snapshot
|
|
83
|
+
self.save(records)
|
|
84
|
+
|
|
85
|
+
def restore(self) -> Iterable[dict[str, Any]]:
|
|
86
|
+
return self.load().values()
|