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.
Files changed (478) hide show
  1. aria_code/__init__.py +42 -0
  2. aria_code/_version.py +20 -0
  3. aria_code/adk_apps/__init__.py +1 -0
  4. aria_code/adk_apps/aria_code_review/__init__.py +1 -0
  5. aria_code/adk_apps/aria_code_review/agent.py +32 -0
  6. aria_code/adk_apps/aria_research/__init__.py +1 -0
  7. aria_code/adk_apps/aria_research/agent.py +31 -0
  8. aria_code/agents/__init__.py +34 -0
  9. aria_code/agents/base.py +318 -0
  10. aria_code/agents/code_review.py +200 -0
  11. aria_code/agents/deep/__init__.py +37 -0
  12. aria_code/agents/deep/calibration_loop.py +144 -0
  13. aria_code/agents/deep/critic.py +125 -0
  14. aria_code/agents/deep/deepen.py +193 -0
  15. aria_code/agents/deep/models.py +149 -0
  16. aria_code/agents/deep/pipeline.py +164 -0
  17. aria_code/agents/deep/quant_fusion.py +192 -0
  18. aria_code/agents/deep/themes.py +95 -0
  19. aria_code/agents/deep/tiers.py +106 -0
  20. aria_code/agents/engineering/__init__.py +14 -0
  21. aria_code/agents/engineering/coder.py +222 -0
  22. aria_code/agents/engineering/tester.py +145 -0
  23. aria_code/agents/financial/__init__.py +21 -0
  24. aria_code/agents/financial/cashflow_burn.py +119 -0
  25. aria_code/agents/financial/catalyst.py +279 -0
  26. aria_code/agents/financial/corporate_finance.py +175 -0
  27. aria_code/agents/financial/debate.py +224 -0
  28. aria_code/agents/financial/earnings.py +303 -0
  29. aria_code/agents/financial/fundamental.py +191 -0
  30. aria_code/agents/financial/macro.py +99 -0
  31. aria_code/agents/financial/news.py +207 -0
  32. aria_code/agents/financial/risk.py +165 -0
  33. aria_code/agents/financial/sector.py +279 -0
  34. aria_code/agents/financial/strategist.py +121 -0
  35. aria_code/agents/financial/stripe_revenue.py +127 -0
  36. aria_code/agents/financial/synthesis.py +274 -0
  37. aria_code/agents/financial/technical.py +258 -0
  38. aria_code/agents/orchestrator.py +44 -0
  39. aria_code/agents/portfolio_agent.py +363 -0
  40. aria_code/agents/realty/__init__.py +62 -0
  41. aria_code/agents/realty/asset_diagnosis.py +152 -0
  42. aria_code/agents/realty/business_match.py +166 -0
  43. aria_code/agents/realty/cashflow_verify.py +209 -0
  44. aria_code/agents/realty/contract_rules.py +210 -0
  45. aria_code/agents/realty/energy_anomaly.py +189 -0
  46. aria_code/agents/realty/exit_settlement.py +208 -0
  47. aria_code/agents/realty/fulfillment_risk.py +206 -0
  48. aria_code/agents/realty/ops_optimize.py +160 -0
  49. aria_code/agents/realty/revenue_share.py +215 -0
  50. aria_code/agents/registry.py +158 -0
  51. aria_code/agents/signal_scheme.py +111 -0
  52. aria_code/agents/sports/__init__.py +0 -0
  53. aria_code/agents/sports/football_agent.py +197 -0
  54. aria_code/agents/supervisor.py +113 -0
  55. aria_code/agents/team.py +492 -0
  56. aria_code/agents/warehouse/__init__.py +25 -0
  57. aria_code/agents/warehouse/contracts.py +26 -0
  58. aria_code/agents/warehouse/dashboard.py +155 -0
  59. aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
  60. aria_code/agents/warehouse/inbound_exceptions.py +25 -0
  61. aria_code/agents/warehouse/inventory_health.py +24 -0
  62. aria_code/agents/warehouse/local_run.py +71 -0
  63. aria_code/agents/warehouse/local_snapshot.py +132 -0
  64. aria_code/agents/warehouse/logistics_cost.py +84 -0
  65. aria_code/agents/warehouse/logistics_sync.py +21 -0
  66. aria_code/agents/warehouse/workflow.py +31 -0
  67. aria_code/aliyun_data_client.py +4 -0
  68. aria_code/apps/README.md +12 -0
  69. aria_code/apps/__init__.py +2 -0
  70. aria_code/apps/channels/README.md +15 -0
  71. aria_code/apps/channels/__init__.py +17 -0
  72. aria_code/apps/channels/intake.py +92 -0
  73. aria_code/apps/channels/registry.py +95 -0
  74. aria_code/apps/channels/tradingview.py +94 -0
  75. aria_code/apps/cli/README.md +13 -0
  76. aria_code/apps/cli/__init__.py +2 -0
  77. aria_code/apps/cli/_optional.py +120 -0
  78. aria_code/apps/cli/bootstrap.py +145 -0
  79. aria_code/apps/cli/broker_render.py +206 -0
  80. aria_code/apps/cli/codegen_paths.py +29 -0
  81. aria_code/apps/cli/commands/__init__.py +16 -0
  82. aria_code/apps/cli/commands/_business_render.py +78 -0
  83. aria_code/apps/cli/commands/_ui.py +71 -0
  84. aria_code/apps/cli/commands/analysis_cmds.py +369 -0
  85. aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
  86. aria_code/apps/cli/commands/auth_cmds.py +239 -0
  87. aria_code/apps/cli/commands/backtest_cmds.py +2543 -0
  88. aria_code/apps/cli/commands/broker_cmds.py +1234 -0
  89. aria_code/apps/cli/commands/business_workflow_cmds.py +319 -0
  90. aria_code/apps/cli/commands/canvas_cmds.py +77 -0
  91. aria_code/apps/cli/commands/catalog.py +87 -0
  92. aria_code/apps/cli/commands/core_cmds.py +2520 -0
  93. aria_code/apps/cli/commands/data_cmds.py +457 -0
  94. aria_code/apps/cli/commands/diagnostic_cmds.py +230 -0
  95. aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
  96. aria_code/apps/cli/commands/file_cmds.py +312 -0
  97. aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
  98. aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
  99. aria_code/apps/cli/commands/market.py +434 -0
  100. aria_code/apps/cli/commands/market_cmds.py +1381 -0
  101. aria_code/apps/cli/commands/market_context.py +425 -0
  102. aria_code/apps/cli/commands/market_render.py +7 -0
  103. aria_code/apps/cli/commands/model_cmds.py +2153 -0
  104. aria_code/apps/cli/commands/ops_cmds.py +721 -0
  105. aria_code/apps/cli/commands/orchestrator_cmds.py +179 -0
  106. aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
  107. aria_code/apps/cli/commands/portfolio_cmds.py +1278 -0
  108. aria_code/apps/cli/commands/report.py +437 -0
  109. aria_code/apps/cli/commands/scaffold_templates.py +617 -0
  110. aria_code/apps/cli/commands/session_cmds.py +236 -0
  111. aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
  112. aria_code/apps/cli/commands/team.py +669 -0
  113. aria_code/apps/cli/commands/team_render.py +8 -0
  114. aria_code/apps/cli/commands/ui_cmds.py +401 -0
  115. aria_code/apps/cli/commands/warehouse_cmds.py +79 -0
  116. aria_code/apps/cli/commands/workflow_cmds.py +480 -0
  117. aria_code/apps/cli/commands/workspace_cmds.py +1494 -0
  118. aria_code/apps/cli/config_paths.py +73 -0
  119. aria_code/apps/cli/config_store.py +78 -0
  120. aria_code/apps/cli/constants.py +88 -0
  121. aria_code/apps/cli/context.py +34 -0
  122. aria_code/apps/cli/deterministic.py +191 -0
  123. aria_code/apps/cli/direct.py +48 -0
  124. aria_code/apps/cli/finance_service_catalog.py +111 -0
  125. aria_code/apps/cli/football_reports.py +231 -0
  126. aria_code/apps/cli/github_app_auth.py +135 -0
  127. aria_code/apps/cli/google_login.py +196 -0
  128. aria_code/apps/cli/handlers/__init__.py +11 -0
  129. aria_code/apps/cli/handlers/broker_handlers.py +122 -0
  130. aria_code/apps/cli/handlers/chart_handlers.py +1309 -0
  131. aria_code/apps/cli/handlers/market_handlers.py +2513 -0
  132. aria_code/apps/cli/handlers/realty_handlers.py +114 -0
  133. aria_code/apps/cli/handlers/sports_handlers.py +22 -0
  134. aria_code/apps/cli/handlers/strategy_advice.py +82 -0
  135. aria_code/apps/cli/helpers.py +302 -0
  136. aria_code/apps/cli/hooks.py +180 -0
  137. aria_code/apps/cli/i18n.py +297 -0
  138. aria_code/apps/cli/intent.py +136 -0
  139. aria_code/apps/cli/intent_router.py +276 -0
  140. aria_code/apps/cli/intent_signals.py +344 -0
  141. aria_code/apps/cli/lifecycle_hooks.py +48 -0
  142. aria_code/apps/cli/local_tool_schemas.py +536 -0
  143. aria_code/apps/cli/main.py +29 -0
  144. aria_code/apps/cli/market_metadata.py +135 -0
  145. aria_code/apps/cli/market_universe.py +487 -0
  146. aria_code/apps/cli/message_processing.py +310 -0
  147. aria_code/apps/cli/model_catalog.py +620 -0
  148. aria_code/apps/cli/pdf_report.py +977 -0
  149. aria_code/apps/cli/plan_mode.py +139 -0
  150. aria_code/apps/cli/plotly_html.py +15 -0
  151. aria_code/apps/cli/prediction_feedback.py +202 -0
  152. aria_code/apps/cli/preflight.py +502 -0
  153. aria_code/apps/cli/project_aria.py +60 -0
  154. aria_code/apps/cli/prompt_assembly.py +59 -0
  155. aria_code/apps/cli/prompts/__init__.py +0 -0
  156. aria_code/apps/cli/prompts/coding.py +53 -0
  157. aria_code/apps/cli/prompts/select.py +142 -0
  158. aria_code/apps/cli/prompts/system_prompts.py +552 -0
  159. aria_code/apps/cli/prompts/ui.py +436 -0
  160. aria_code/apps/cli/provider_endpoints.py +237 -0
  161. aria_code/apps/cli/provider_keys.py +73 -0
  162. aria_code/apps/cli/providers/__init__.py +1 -0
  163. aria_code/apps/cli/providers/base.py +521 -0
  164. aria_code/apps/cli/providers/chat_routing.py +128 -0
  165. aria_code/apps/cli/providers/collaboration.py +98 -0
  166. aria_code/apps/cli/providers/llm/__init__.py +1 -0
  167. aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
  168. aria_code/apps/cli/providers/llm/ollama_stream.py +1594 -0
  169. aria_code/apps/cli/providers/llm/response_cache.py +60 -0
  170. aria_code/apps/cli/providers/llm/sse_stream.py +257 -0
  171. aria_code/apps/cli/providers/runtime_bridge.py +353 -0
  172. aria_code/apps/cli/providers/vertexai_stream.py +316 -0
  173. aria_code/apps/cli/runtime_consumer.py +657 -0
  174. aria_code/apps/cli/sandbox.py +32 -0
  175. aria_code/apps/cli/session_export.py +120 -0
  176. aria_code/apps/cli/session_jsonl.py +208 -0
  177. aria_code/apps/cli/session_store.py +112 -0
  178. aria_code/apps/cli/skills_catalog.py +273 -0
  179. aria_code/apps/cli/todo_tracker.py +190 -0
  180. aria_code/apps/cli/tool_executor.py +823 -0
  181. aria_code/apps/cli/tool_registry.py +34 -0
  182. aria_code/apps/cli/tool_scope.py +106 -0
  183. aria_code/apps/cli/tools/__init__.py +40 -0
  184. aria_code/apps/cli/tools/context.py +46 -0
  185. aria_code/apps/cli/tools/file_tools.py +180 -0
  186. aria_code/apps/cli/tools/market_tools.py +620 -0
  187. aria_code/apps/cli/tools/notebook_tools.py +111 -0
  188. aria_code/apps/cli/tools/system_tools.py +726 -0
  189. aria_code/apps/cli/tools/write_tools.py +845 -0
  190. aria_code/apps/cli/tradingview_bridge.py +529 -0
  191. aria_code/apps/cli/turn_planning.py +137 -0
  192. aria_code/apps/cli/update_check.py +153 -0
  193. aria_code/apps/cli/utils/__init__.py +0 -0
  194. aria_code/apps/cli/utils/market_detect.py +1597 -0
  195. aria_code/apps/daemon/README.md +14 -0
  196. aria_code/apps/vscode/README.md +115 -0
  197. aria_code/apps/vscode/package.json +70 -0
  198. aria_code/aria_cli.py +6743 -0
  199. aria_code/aria_daemon.py +1398 -0
  200. aria_code/aria_feishu_bot.py +1423 -0
  201. aria_code/aria_mcp_server.py +7 -0
  202. aria_code/aria_relay_client.py +4 -0
  203. aria_code/aria_relay_server.py +490 -0
  204. aria_code/aria_telegram_bot.py +202 -0
  205. aria_code/ariarc.py +385 -0
  206. aria_code/artifacts.py +581 -0
  207. aria_code/backtest_engine.py +4 -0
  208. aria_code/backtest_report.py +4 -0
  209. aria_code/brokers/__init__.py +78 -0
  210. aria_code/brokers/automation.py +193 -0
  211. aria_code/brokers/base.py +231 -0
  212. aria_code/brokers/capabilities.py +264 -0
  213. aria_code/brokers/cn/__init__.py +10 -0
  214. aria_code/brokers/cn/easytrader_broker.py +193 -0
  215. aria_code/brokers/cn/futu_broker.py +194 -0
  216. aria_code/brokers/cn/longbridge_broker.py +190 -0
  217. aria_code/brokers/cn/tiger_broker.py +196 -0
  218. aria_code/brokers/cn/xtquant_broker.py +225 -0
  219. aria_code/brokers/config.py +414 -0
  220. aria_code/brokers/intl/__init__.py +5 -0
  221. aria_code/brokers/intl/alpaca_broker.py +183 -0
  222. aria_code/brokers/intl/ibkr_broker.py +215 -0
  223. aria_code/brokers/intl/trading212_broker.py +183 -0
  224. aria_code/brokers/intl/webull_broker.py +156 -0
  225. aria_code/brokers/paper_broker.py +266 -0
  226. aria_code/brokers/planning.py +296 -0
  227. aria_code/brokers/registry.py +213 -0
  228. aria_code/brokers/trading.py +267 -0
  229. aria_code/canva_client.py +4 -0
  230. aria_code/change_store.py +127 -0
  231. aria_code/clients/__init__.py +0 -0
  232. aria_code/clients/aliyun_data_client.py +660 -0
  233. aria_code/clients/aria_relay_client.py +182 -0
  234. aria_code/clients/canva_client.py +284 -0
  235. aria_code/clients/figma_client.py +100 -0
  236. aria_code/clients/football_data_client.py +1671 -0
  237. aria_code/clients/kling_video_client.py +182 -0
  238. aria_code/clients/market_data_client.py +1976 -0
  239. aria_code/clients/openai_image_client.py +242 -0
  240. aria_code/clients/runway_video_client.py +157 -0
  241. aria_code/clients/warehouse_erp_client.py +105 -0
  242. aria_code/cloud_memory.py +181 -0
  243. aria_code/command_safety.py +19 -0
  244. aria_code/computer_use_tools.py +4 -0
  245. aria_code/dashboard_generator.py +579 -0
  246. aria_code/data_analysis_tools.py +4 -0
  247. aria_code/data_cleaner.py +483 -0
  248. aria_code/data_service.py +513 -0
  249. aria_code/datasources/__init__.py +23 -0
  250. aria_code/datasources/base.py +166 -0
  251. aria_code/datasources/router.py +253 -0
  252. aria_code/datasources/sources/__init__.py +15 -0
  253. aria_code/datasources/sources/akshare_source.py +269 -0
  254. aria_code/datasources/sources/alpha_vantage_source.py +203 -0
  255. aria_code/datasources/sources/edgar_source.py +219 -0
  256. aria_code/datasources/sources/finnhub_source.py +198 -0
  257. aria_code/datasources/sources/fred_source.py +223 -0
  258. aria_code/datasources/sources/tushare_source.py +143 -0
  259. aria_code/datasources/sources/web_scraper_source.py +278 -0
  260. aria_code/datasources/sources/world_bank_source.py +206 -0
  261. aria_code/datasources/sources/yfinance_source.py +152 -0
  262. aria_code/demo_player.py +207 -0
  263. aria_code/doctor.py +813 -0
  264. aria_code/domain/__init__.py +0 -0
  265. aria_code/domain/backtest_engine.py +354 -0
  266. aria_code/domain/backtest_report.py +472 -0
  267. aria_code/domain/finance_formulas.py +389 -0
  268. aria_code/domain/portfolio_ledger.py +307 -0
  269. aria_code/domain/strategy_vault.py +421 -0
  270. aria_code/evals/__init__.py +37 -0
  271. aria_code/evals/harness.py +598 -0
  272. aria_code/evals/runner.py +238 -0
  273. aria_code/external_agent_runner.py +115 -0
  274. aria_code/figma_client.py +4 -0
  275. aria_code/file_analysis_tools.py +4 -0
  276. aria_code/finance_formulas.py +4 -0
  277. aria_code/football_data_client.py +4 -0
  278. aria_code/image_gen_tools.py +4 -0
  279. aria_code/intent_classifier.py +386 -0
  280. aria_code/kling_video_client.py +4 -0
  281. aria_code/licensing.py +151 -0
  282. aria_code/local_finance_tools.py +4 -0
  283. aria_code/local_image_provider.py +4 -0
  284. aria_code/local_llm_provider.py +4 -0
  285. aria_code/macro_tools.py +4 -0
  286. aria_code/markdown_pdf.py +289 -0
  287. aria_code/market_data_client.py +4 -0
  288. aria_code/mcp_client.py +639 -0
  289. aria_code/memory_manager.py +246 -0
  290. aria_code/model_capability.py +486 -0
  291. aria_code/notification_tools.py +4 -0
  292. aria_code/openai_image_client.py +4 -0
  293. aria_code/packages/__init__.py +23 -0
  294. aria_code/packages/adk_bridge/__init__.py +11 -0
  295. aria_code/packages/adk_bridge/code_review_tools.py +49 -0
  296. aria_code/packages/adk_bridge/market_tools.py +125 -0
  297. aria_code/packages/aria_agents/__init__.py +5 -0
  298. aria_code/packages/aria_agents/manifest.py +78 -0
  299. aria_code/packages/aria_core/__init__.py +34 -0
  300. aria_code/packages/aria_core/architecture.py +269 -0
  301. aria_code/packages/aria_core/export.py +147 -0
  302. aria_code/packages/aria_core/manifest.py +65 -0
  303. aria_code/packages/aria_core/paths.py +57 -0
  304. aria_code/packages/aria_core/secure_file.py +68 -0
  305. aria_code/packages/aria_infra/__init__.py +15 -0
  306. aria_code/packages/aria_infra/arthera.py +52 -0
  307. aria_code/packages/aria_infra/doctor.py +246 -0
  308. aria_code/packages/aria_infra/product.py +40 -0
  309. aria_code/packages/aria_mcp/__init__.py +25 -0
  310. aria_code/packages/aria_mcp/bridge.py +201 -0
  311. aria_code/packages/aria_mcp/circuit.py +82 -0
  312. aria_code/packages/aria_mcp/config.py +111 -0
  313. aria_code/packages/aria_mcp/installer.py +95 -0
  314. aria_code/packages/aria_mcp/server.py +1550 -0
  315. aria_code/packages/aria_mcp/tools.py +81 -0
  316. aria_code/packages/aria_sdk/__init__.py +19 -0
  317. aria_code/packages/aria_sdk/client.py +396 -0
  318. aria_code/packages/aria_sdk/providers.py +70 -0
  319. aria_code/packages/aria_sdk/streaming.py +79 -0
  320. aria_code/packages/aria_sdk/types.py +86 -0
  321. aria_code/packages/aria_services/__init__.py +92 -0
  322. aria_code/packages/aria_services/cache.py +60 -0
  323. aria_code/packages/aria_services/context.py +356 -0
  324. aria_code/packages/aria_services/context_checkpoints.py +157 -0
  325. aria_code/packages/aria_services/data.py +11 -0
  326. aria_code/packages/aria_services/provider_health.py +268 -0
  327. aria_code/packages/aria_services/references.py +247 -0
  328. aria_code/packages/aria_services/registry.py +249 -0
  329. aria_code/packages/aria_services/research_protocol.py +57 -0
  330. aria_code/packages/aria_services/research_quality.py +186 -0
  331. aria_code/packages/aria_services/settings.py +157 -0
  332. aria_code/packages/aria_services/usage.py +151 -0
  333. aria_code/packages/aria_skills/__init__.py +46 -0
  334. aria_code/packages/aria_skills/catalog.py +149 -0
  335. aria_code/packages/aria_skills/loader.py +455 -0
  336. aria_code/packages/aria_skills/registry.py +84 -0
  337. aria_code/packages/aria_tools/__init__.py +5 -0
  338. aria_code/packages/aria_tools/financial/__init__.py +8 -0
  339. aria_code/packages/aria_tools/financial/_paths.py +130 -0
  340. aria_code/packages/aria_tools/financial/compliance.py +31 -0
  341. aria_code/packages/aria_tools/financial/factors.py +33 -0
  342. aria_code/packages/aria_tools/financial/risk_tools.py +33 -0
  343. aria_code/packages/aria_tools/financial/strategy.py +31 -0
  344. aria_code/packages/aria_tools/registry.py +128 -0
  345. aria_code/packages/quant_engine/__init__.py +34 -0
  346. aria_code/packages/quant_engine/_namespace.py +62 -0
  347. aria_code/packages/quant_engine/backtest/__init__.py +14 -0
  348. aria_code/packages/quant_engine/backtest/core.py +53 -0
  349. aria_code/packages/quant_engine/backtest/engine.py +96 -0
  350. aria_code/packages/quant_engine/portfolio/__init__.py +13 -0
  351. aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
  352. aria_code/packages/quant_engine/sports/__init__.py +80 -0
  353. aria_code/packages/quant_engine/sports/calibrator.py +354 -0
  354. aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
  355. aria_code/packages/quant_engine/sports/elo.py +300 -0
  356. aria_code/packages/quant_engine/sports/form.py +188 -0
  357. aria_code/packages/quant_engine/sports/h2h.py +195 -0
  358. aria_code/packages/quant_engine/sports/ml_model.py +377 -0
  359. aria_code/packages/quant_engine/sports/predictor.py +312 -0
  360. aria_code/packages/quant_engine/sports/tracker.py +665 -0
  361. aria_code/packages/quant_engine/stochastic/__init__.py +35 -0
  362. aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  363. aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
  364. aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  365. aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  366. aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
  367. aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  368. aria_code/packs/__init__.py +85 -0
  369. aria_code/packs/base.py +168 -0
  370. aria_code/packs/finance/__init__.py +250 -0
  371. aria_code/packs/logistics/__init__.py +169 -0
  372. aria_code/packs/payments/__init__.py +139 -0
  373. aria_code/packs/realty/__init__.py +190 -0
  374. aria_code/packs/registry.py +202 -0
  375. aria_code/packs/rules.py +90 -0
  376. aria_code/plan_utils.py +194 -0
  377. aria_code/plugin_loader.py +328 -0
  378. aria_code/portfolio_ledger.py +4 -0
  379. aria_code/preview_server.py +449 -0
  380. aria_code/privacy/__init__.py +5 -0
  381. aria_code/privacy/feedback.py +123 -0
  382. aria_code/project_review.py +278 -0
  383. aria_code/project_review_api.py +235 -0
  384. aria_code/project_review_server.py +18 -0
  385. aria_code/project_tools.py +4 -0
  386. aria_code/providers/__init__.py +30 -0
  387. aria_code/providers/llm/__init__.py +19 -0
  388. aria_code/providers/llm/anthropic.py +184 -0
  389. aria_code/providers/llm/autoconfig.py +213 -0
  390. aria_code/providers/llm/base.py +139 -0
  391. aria_code/providers/llm/custom.py +158 -0
  392. aria_code/providers/llm/ollama.py +129 -0
  393. aria_code/providers/llm/openai_compat.py +313 -0
  394. aria_code/providers/llm/registry.py +444 -0
  395. aria_code/providers/local_image_provider.py +202 -0
  396. aria_code/providers/local_llm_provider.py +589 -0
  397. aria_code/realty_data_tools.py +4 -0
  398. aria_code/report_exporters.py +150 -0
  399. aria_code/report_generator.py +1764 -0
  400. aria_code/runtime/__init__.py +169 -0
  401. aria_code/runtime/acceptance.py +481 -0
  402. aria_code/runtime/agent_loop.py +1520 -0
  403. aria_code/runtime/approval.py +57 -0
  404. aria_code/runtime/budget.py +232 -0
  405. aria_code/runtime/checkpoints.py +432 -0
  406. aria_code/runtime/crash_report.py +174 -0
  407. aria_code/runtime/events.py +113 -0
  408. aria_code/runtime/gateway.py +150 -0
  409. aria_code/runtime/lsp.py +484 -0
  410. aria_code/runtime/orchestrator.py +55 -0
  411. aria_code/runtime/repo_map.py +700 -0
  412. aria_code/runtime/run_state.py +127 -0
  413. aria_code/runtime/run_store.py +450 -0
  414. aria_code/runtime/self_healing.py +422 -0
  415. aria_code/runtime/subagent.py +547 -0
  416. aria_code/runtime/task_ledger.py +86 -0
  417. aria_code/runtime/task_orchestrator.py +90 -0
  418. aria_code/runtime/tool_executor.py +217 -0
  419. aria_code/runtime/tool_policy.py +107 -0
  420. aria_code/runtime/worktrees.py +215 -0
  421. aria_code/runway_video_client.py +4 -0
  422. aria_code/safety/__init__.py +23 -0
  423. aria_code/safety/permissions.py +306 -0
  424. aria_code/safety/service.py +102 -0
  425. aria_code/setup_wizard.py +673 -0
  426. aria_code/spreadsheet_tools.py +4 -0
  427. aria_code/strategy_vault.py +4 -0
  428. aria_code/tools/__init__.py +0 -0
  429. aria_code/tools/artifact_tools.py +252 -0
  430. aria_code/tools/broker_tools.py +92 -0
  431. aria_code/tools/build_quant_engine.py +97 -0
  432. aria_code/tools/code_audit_tools.py +192 -0
  433. aria_code/tools/computer_use_tools.py +504 -0
  434. aria_code/tools/data_analysis_tools.py +809 -0
  435. aria_code/tools/enterprise_finance_tools.py +156 -0
  436. aria_code/tools/extended_tools.py +240 -0
  437. aria_code/tools/file_analysis_tools.py +885 -0
  438. aria_code/tools/image_gen_tools.py +114 -0
  439. aria_code/tools/local_finance_tools.py +3417 -0
  440. aria_code/tools/logistics_tools.py +135 -0
  441. aria_code/tools/macro_tools.py +369 -0
  442. aria_code/tools/notification_tools.py +249 -0
  443. aria_code/tools/project_tools.py +525 -0
  444. aria_code/tools/realty_data_tools.py +659 -0
  445. aria_code/tools/spreadsheet_tools.py +388 -0
  446. aria_code/tools/stripe_tools.py +115 -0
  447. aria_code/ui/__init__.py +105 -0
  448. aria_code/ui/banner.py +508 -0
  449. aria_code/ui/completer.py +501 -0
  450. aria_code/ui/console.py +281 -0
  451. aria_code/ui/image_render.py +243 -0
  452. aria_code/ui/input_box.py +488 -0
  453. aria_code/ui/picker.py +201 -0
  454. aria_code/ui/render/__init__.py +11 -0
  455. aria_code/ui/render/finance.py +1705 -0
  456. aria_code/ui/render/market.py +225 -0
  457. aria_code/ui/render/output.py +775 -0
  458. aria_code/ui/render/responsive.py +75 -0
  459. aria_code/ui/render/team.py +354 -0
  460. aria_code/ui/robot.py +235 -0
  461. aria_code/ui/startup_dashboard.py +109 -0
  462. aria_code/utils/vscode_uri.py +37 -0
  463. aria_code/video_analysis.py +147 -0
  464. aria_code/video_editor.py +271 -0
  465. aria_code/workspace/__init__.py +6 -0
  466. aria_code/workspace/files.py +193 -0
  467. aria_code/workspace/verify.py +113 -0
  468. aria_code/workspace_changes.py +199 -0
  469. aria_code/workspace_context.py +233 -0
  470. aria_code/workspace_service.py +196 -0
  471. aria_code/workspace_tools.py +150 -0
  472. aria_code-0.52.0.dist-info/METADATA +237 -0
  473. aria_code-0.52.0.dist-info/RECORD +478 -0
  474. aria_code-0.52.0.dist-info/WHEEL +5 -0
  475. aria_code-0.52.0.dist-info/entry_points.txt +3 -0
  476. aria_code-0.52.0.dist-info/licenses/LICENSE +201 -0
  477. aria_code-0.52.0.dist-info/licenses/NOTICE +49 -0
  478. 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()