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,157 @@
1
+ """Compaction checkpoints — persist the pre-compaction conversation.
2
+
3
+ Resolves the context layer's long-standing blocker (artifact schema and
4
+ retention policy, decided 2026-07): every compaction first appends a
5
+ full-snapshot checkpoint, so the conversation state that compaction is about
6
+ to destroy becomes restorable instead of gone.
7
+
8
+ Schema ``aria.context_checkpoint.v1`` — one JSON object per line:
9
+ schema, ts, session_id, seq, kind ("compact" | "hard" | "fallback"),
10
+ message_count, truncated, envelope (optional compaction metadata),
11
+ messages (the full pre-compaction conversation; oldest messages dropped
12
+ first if the serialized snapshot would exceed the per-record byte cap).
13
+
14
+ Storage ``<root>/<session_id>.jsonl`` (append-only, crash-safe, greppable —
15
+ same conventions as the session JSONL store and the durable run store).
16
+
17
+ Retention (enforced on every write, config-overridable by the caller):
18
+ keep the newest ``keep_per_session`` records per session, and delete
19
+ sibling session files untouched for ``max_age_days``.
20
+
21
+ Pure besides filesystem writes under ``root``; clock injectable for tests.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import json
27
+ import time
28
+ from pathlib import Path
29
+ from typing import Any, Callable, Dict, List, Optional
30
+ from aria_code.packages.aria_core.paths import aria_home
31
+
32
+ CHECKPOINT_SCHEMA = "aria.context_checkpoint.v1"
33
+ DEFAULT_KEEP_PER_SESSION = 5
34
+ DEFAULT_MAX_AGE_DAYS = 30
35
+ DEFAULT_MAX_SNAPSHOT_BYTES = 2 * 1024 * 1024
36
+
37
+
38
+ class ContextCheckpointStore:
39
+ def __init__(
40
+ self,
41
+ root: Path,
42
+ *,
43
+ keep_per_session: int = DEFAULT_KEEP_PER_SESSION,
44
+ max_age_days: int = DEFAULT_MAX_AGE_DAYS,
45
+ max_snapshot_bytes: int = DEFAULT_MAX_SNAPSHOT_BYTES,
46
+ clock: Callable[[], float] = time.time,
47
+ ) -> None:
48
+ self.root = Path(root)
49
+ self.keep_per_session = max(1, int(keep_per_session))
50
+ self.max_age_days = max(1, int(max_age_days))
51
+ self.max_snapshot_bytes = max(64 * 1024, int(max_snapshot_bytes))
52
+ self.clock = clock
53
+
54
+ # ── write path ───────────────────────────────────────────────────────────
55
+
56
+ def record(
57
+ self,
58
+ session_id: str,
59
+ messages: List[dict],
60
+ *,
61
+ kind: str = "compact",
62
+ envelope: Optional[Dict[str, Any]] = None,
63
+ ) -> Dict[str, Any]:
64
+ """Append one checkpoint for *session_id*; returns the written record."""
65
+ self.root.mkdir(parents=True, exist_ok=True)
66
+ existing = self.list(session_id)
67
+ seq = (existing[-1]["seq"] + 1) if existing else 1
68
+
69
+ snapshot, truncated = self._bounded_snapshot(list(messages or []))
70
+ record = {
71
+ "schema": CHECKPOINT_SCHEMA,
72
+ "ts": self.clock(),
73
+ "session_id": str(session_id),
74
+ "seq": seq,
75
+ "kind": kind,
76
+ "message_count": len(messages or []),
77
+ "truncated": truncated,
78
+ "envelope": dict(envelope) if envelope else {},
79
+ "messages": snapshot,
80
+ }
81
+
82
+ kept = (existing + [record])[-self.keep_per_session:]
83
+ path = self._path(session_id)
84
+ tmp = path.with_suffix(".jsonl.tmp")
85
+ tmp.write_text(
86
+ "".join(json.dumps(r, ensure_ascii=False, default=str) + "\n" for r in kept),
87
+ encoding="utf-8",
88
+ )
89
+ tmp.replace(path)
90
+ self._expire_old_files()
91
+ return record
92
+
93
+ def _bounded_snapshot(self, messages: List[dict]) -> tuple:
94
+ """Drop oldest messages until the serialized snapshot fits the cap."""
95
+ truncated = False
96
+ while messages:
97
+ blob = json.dumps(messages, ensure_ascii=False, default=str)
98
+ if len(blob.encode("utf-8")) <= self.max_snapshot_bytes:
99
+ return messages, truncated
100
+ drop = max(1, len(messages) // 10)
101
+ messages = messages[drop:]
102
+ truncated = True
103
+ return [], truncated
104
+
105
+ def _expire_old_files(self) -> None:
106
+ cutoff = self.clock() - self.max_age_days * 86400
107
+ try:
108
+ for f in self.root.glob("*.jsonl"):
109
+ if f.stat().st_mtime < cutoff:
110
+ f.unlink(missing_ok=True)
111
+ except Exception:
112
+ pass # retention is best-effort; never break the write path
113
+
114
+ # ── read path ────────────────────────────────────────────────────────────
115
+
116
+ def list(self, session_id: str) -> List[Dict[str, Any]]:
117
+ path = self._path(session_id)
118
+ if not path.exists():
119
+ return []
120
+ records: List[Dict[str, Any]] = []
121
+ for line in path.read_text(encoding="utf-8").splitlines():
122
+ line = line.strip()
123
+ if not line:
124
+ continue
125
+ try:
126
+ obj = json.loads(line)
127
+ except json.JSONDecodeError:
128
+ continue
129
+ if isinstance(obj, dict) and obj.get("schema") == CHECKPOINT_SCHEMA:
130
+ records.append(obj)
131
+ return records
132
+
133
+ def latest(self, session_id: str) -> Optional[Dict[str, Any]]:
134
+ records = self.list(session_id)
135
+ return records[-1] if records else None
136
+
137
+ @staticmethod
138
+ def restore_messages(record: Dict[str, Any]) -> List[dict]:
139
+ """The conversation as it stood before that compaction."""
140
+ msgs = record.get("messages")
141
+ return [dict(m) for m in msgs] if isinstance(msgs, list) else []
142
+
143
+ def _path(self, session_id: str) -> Path:
144
+ safe = "".join(c if c.isalnum() or c in "-_" else "_" for c in str(session_id)) or "session"
145
+ return self.root / f"{safe}.jsonl"
146
+
147
+
148
+ def default_checkpoint_root() -> Path:
149
+ """Same home-resolution rule as the durable run store."""
150
+ import os
151
+ configured = os.getenv("ARIA_HOME")
152
+ if configured:
153
+ root = Path(configured).expanduser()
154
+ else:
155
+ legacy = aria_home()
156
+ root = legacy if legacy.exists() else Path.home() / ".aria-code"
157
+ return root / "runtime" / "context_checkpoints"
@@ -0,0 +1,11 @@
1
+ """Data service facade for package-level imports."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from aria_code.data_service import DataBundle, DataService, DataServiceResult
6
+
7
+ __all__ = [
8
+ "DataBundle",
9
+ "DataService",
10
+ "DataServiceResult",
11
+ ]
@@ -0,0 +1,268 @@
1
+ """Provider error classification and lightweight health state."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from dataclasses import asdict, dataclass
7
+ from typing import Any, Dict, List
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class ProviderIssue:
12
+ provider: str
13
+ category: str
14
+ message: str
15
+ retryable: bool = True
16
+ cooldown_seconds: int = 0
17
+
18
+ def to_dict(self) -> Dict[str, Any]:
19
+ return asdict(self)
20
+
21
+
22
+ @dataclass
23
+ class ProviderState:
24
+ provider: str
25
+ status: str = "ok"
26
+ last_error_category: str = ""
27
+ last_error: str = ""
28
+ failures: int = 0
29
+ cooldown_until: float = 0.0
30
+ last_seen_at: float = 0.0
31
+ last_success_at: float = 0.0
32
+
33
+ def to_dict(self) -> Dict[str, Any]:
34
+ data = asdict(self)
35
+ data["cooldown_active"] = self.cooldown_until > time.time()
36
+ data["cooldown_remaining_seconds"] = max(0, int(self.cooldown_until - time.time()))
37
+ return data
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class ProviderHealthSummary:
42
+ schema: str
43
+ total: int
44
+ ok: int
45
+ warn: int
46
+ err: int
47
+ cooldown: int
48
+ auth_errors: int
49
+ providers: List[str]
50
+ status: str
51
+ detail: str
52
+ suggestion: str
53
+
54
+ def to_dict(self) -> Dict[str, Any]:
55
+ return asdict(self)
56
+
57
+
58
+ _PUBLIC_SERVICE_LABELS = {
59
+ "market_data": "市场数据",
60
+ "ai": "AI 助手",
61
+ }
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class PublicServiceStatus:
66
+ """A safe, product-facing view of internal provider health."""
67
+
68
+ schema: str
69
+ service: str
70
+ state: str
71
+ label: str
72
+ message: str
73
+ can_retry: bool
74
+
75
+ def to_dict(self) -> Dict[str, Any]:
76
+ return asdict(self)
77
+
78
+
79
+ def public_service_status(
80
+ service: str, snapshot: List[Dict[str, Any]] | None = None
81
+ ) -> PublicServiceStatus:
82
+ """Project provider health into copy that is safe to render in the product.
83
+
84
+ Provider names, endpoint details and raw failures remain internal diagnostics.
85
+ """
86
+
87
+ service_key = str(service or "").strip().lower() or "service"
88
+ label = _PUBLIC_SERVICE_LABELS.get(service_key, "服务")
89
+ rows = list(snapshot or [])
90
+
91
+ if not rows:
92
+ return PublicServiceStatus(
93
+ schema="aria.public_service_status.v1",
94
+ service=service_key,
95
+ state="unknown",
96
+ label=label,
97
+ message=f"{label}状态将在首次使用后更新。",
98
+ can_retry=False,
99
+ )
100
+
101
+ states = [str(row.get("status") or "unknown") for row in rows]
102
+ categories = [str(row.get("last_error_category") or "") for row in rows]
103
+ available_count = sum(state == "ok" for state in states)
104
+
105
+ if available_count == len(rows):
106
+ state, message, can_retry = "available", f"{label}可用。", False
107
+ elif available_count:
108
+ state, message, can_retry = (
109
+ "degraded",
110
+ f"{label}已自动切换到可用服务。",
111
+ True,
112
+ )
113
+ elif "auth" in categories:
114
+ state, message, can_retry = (
115
+ "unavailable",
116
+ f"{label}暂不可用,需要检查连接设置。",
117
+ False,
118
+ )
119
+ else:
120
+ state, message, can_retry = "unavailable", f"{label}暂不可用,请稍后重试。", True
121
+
122
+ return PublicServiceStatus(
123
+ schema="aria.public_service_status.v1",
124
+ service=service_key,
125
+ state=state,
126
+ label=label,
127
+ message=message,
128
+ can_retry=can_retry,
129
+ )
130
+
131
+
132
+ def classify_provider_error(provider: str, error: Any) -> ProviderIssue:
133
+ text = str(error or "").strip()
134
+ low = text.lower()
135
+ if not text:
136
+ return ProviderIssue(provider, "unavailable", "provider returned no usable data", True, 30)
137
+ if any(token in low for token in ("429", "rate", "too many", "limit")):
138
+ return ProviderIssue(provider, "rate_limited", "provider rate limited the request", True, 60)
139
+ if any(token in low for token in ("timeout", "timed out", "curl: (28)", "read timed out")):
140
+ return ProviderIssue(provider, "timeout", "provider request timed out", True, 30)
141
+ if any(token in low for token in ("connection", "network", "refused", "remote", "dns", "name resolution")):
142
+ return ProviderIssue(provider, "network", "provider network connection failed", True, 30)
143
+ if any(token in low for token in ("empty", "no data", "not found", "none", "null")):
144
+ return ProviderIssue(provider, "no_data", "provider returned no market data", True, 15)
145
+ if any(token in low for token in ("unauthorized", "forbidden", "api key", "401", "403")):
146
+ return ProviderIssue(provider, "auth", "provider authentication failed", False, 0)
147
+ # Do not retain raw provider errors here. They can contain URLs, request
148
+ # headers, or tokens and this state is consumed by product diagnostics.
149
+ return ProviderIssue(provider, "error", "provider request failed", True, 30)
150
+
151
+
152
+ class ProviderHealthRegistry:
153
+ """In-process health state for data providers."""
154
+
155
+ def __init__(self) -> None:
156
+ self._states: Dict[str, ProviderState] = {}
157
+
158
+ def mark_success(self, provider: str) -> None:
159
+ if not provider:
160
+ return
161
+ state = self._states.setdefault(provider, ProviderState(provider=provider))
162
+ state.status = "ok"
163
+ state.last_error_category = ""
164
+ state.last_error = ""
165
+ state.failures = 0
166
+ state.cooldown_until = 0.0
167
+ now = time.time()
168
+ state.last_seen_at = now
169
+ state.last_success_at = now
170
+
171
+ def mark_issue(self, issue: ProviderIssue) -> None:
172
+ if not issue.provider:
173
+ return
174
+ state = self._states.setdefault(issue.provider, ProviderState(provider=issue.provider))
175
+ now = time.time()
176
+ state.status = issue.category
177
+ state.last_error_category = issue.category
178
+ state.last_error = issue.message
179
+ state.failures += 1
180
+ state.last_seen_at = now
181
+ if issue.cooldown_seconds:
182
+ state.cooldown_until = max(state.cooldown_until, now + issue.cooldown_seconds)
183
+
184
+ def provider_in_cooldown(self, provider: str) -> bool:
185
+ state = self._states.get(provider)
186
+ return bool(state and state.cooldown_until > time.time())
187
+
188
+ def snapshot(self) -> List[Dict[str, Any]]:
189
+ return [self._states[name].to_dict() for name in sorted(self._states)]
190
+
191
+ def summary(self) -> ProviderHealthSummary:
192
+ return summarize_provider_health(self.snapshot())
193
+
194
+ def public_status(self, service: str) -> PublicServiceStatus:
195
+ return public_service_status(service, self.snapshot())
196
+
197
+
198
+ GLOBAL_PROVIDER_HEALTH = ProviderHealthRegistry()
199
+
200
+
201
+ def summarize_provider_health(snapshot: List[Dict[str, Any]] | None = None) -> ProviderHealthSummary:
202
+ rows = list(snapshot or [])
203
+ if not rows:
204
+ return ProviderHealthSummary(
205
+ schema="aria.provider_health_summary.v1",
206
+ total=0,
207
+ ok=0,
208
+ warn=0,
209
+ err=0,
210
+ cooldown=0,
211
+ auth_errors=0,
212
+ providers=[],
213
+ status="warn",
214
+ detail="no provider calls recorded in this session",
215
+ suggestion="Run /quote, /ta, /analyze, or /report to populate provider health.",
216
+ )
217
+
218
+ ok = warn = err = cooldown = auth_errors = 0
219
+ providers: list[str] = []
220
+ for row in rows:
221
+ provider = str(row.get("provider") or "provider")
222
+ providers.append(provider)
223
+ status = str(row.get("status") or "unknown")
224
+ error_category = str(row.get("last_error_category") or "")
225
+ if status == "ok":
226
+ ok += 1
227
+ elif error_category == "auth":
228
+ err += 1
229
+ auth_errors += 1
230
+ else:
231
+ warn += 1
232
+ if row.get("cooldown_active"):
233
+ cooldown += 1
234
+
235
+ if err:
236
+ status = "err"
237
+ elif warn or cooldown:
238
+ status = "warn"
239
+ else:
240
+ status = "ok"
241
+
242
+ parts = [f"{len(rows)} providers"]
243
+ if ok:
244
+ parts.append(f"{ok} ok")
245
+ if warn:
246
+ parts.append(f"{warn} warn")
247
+ if err:
248
+ parts.append(f"{err} err")
249
+ if cooldown:
250
+ parts.append(f"{cooldown} cooldown")
251
+
252
+ suggestion = "Run /doctor --network or inspect /apikey." if status != "ok" else "All providers healthy."
253
+ if auth_errors:
254
+ suggestion = "Fix API keys first, then retry /doctor or /cloud health."
255
+
256
+ return ProviderHealthSummary(
257
+ schema="aria.provider_health_summary.v1",
258
+ total=len(rows),
259
+ ok=ok,
260
+ warn=warn,
261
+ err=err,
262
+ cooldown=cooldown,
263
+ auth_errors=auth_errors,
264
+ providers=providers,
265
+ status=status,
266
+ detail=", ".join(parts),
267
+ suggestion=suggestion,
268
+ )
@@ -0,0 +1,247 @@
1
+ """Typed, read-only ``@`` context references for Aria inputs.
2
+
3
+ ``/`` selects an operation. ``@`` selects context for that operation or for a
4
+ natural-language request. This service deliberately has no terminal or LLM
5
+ dependency so the CLI, SDK, and future clients share the same semantics.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import re
12
+ import shlex
13
+ from dataclasses import dataclass, field
14
+ from pathlib import Path
15
+ from typing import Iterable, Iterator, Sequence
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class ReferenceKind:
20
+ name: str
21
+ label_en: str
22
+ label_zh: str
23
+ filesystem: bool = False
24
+
25
+
26
+ REFERENCE_KINDS: tuple[ReferenceKind, ...] = (
27
+ ReferenceKind("file", "File", "文件", filesystem=True),
28
+ ReferenceKind("folder", "Folder", "目录", filesystem=True),
29
+ ReferenceKind("asset", "Market asset", "市场资产"),
30
+ ReferenceKind("portfolio", "Portfolio", "投资组合", filesystem=True),
31
+ ReferenceKind("strategy", "Strategy", "策略", filesystem=True),
32
+ ReferenceKind("dataset", "Dataset", "数据集", filesystem=True),
33
+ ReferenceKind("run", "Research run", "研究运行", filesystem=True),
34
+ ReferenceKind("report", "Report", "报告", filesystem=True),
35
+ )
36
+
37
+ _KIND_MAP = {item.name: item for item in REFERENCE_KINDS}
38
+ _REF_RE = re.compile(
39
+ r"(?<![\w@])@(?:(?P<kind>[A-Za-z][A-Za-z0-9_-]*):)?"
40
+ r"(?P<value>\"[^\"\n]+\"|'[^'\n]+'|[^\s@]+)"
41
+ )
42
+ _TRAILING_PUNCTUATION = ",;!?,。;!?)]}"
43
+ _ASSET_RE = re.compile(r"^[A-Za-z0-9^._=-]{1,32}$")
44
+ _RESOURCE_DIRS = {
45
+ "portfolio": ("portfolios",),
46
+ "strategy": ("strategies",),
47
+ "dataset": ("datasets", "data"),
48
+ "run": ("runs", "reports"),
49
+ "report": ("reports", "generated"),
50
+ }
51
+ _RESOURCE_EXTENSIONS = ("", ".md", ".json", ".yaml", ".yml", ".toml", ".csv", ".html")
52
+
53
+
54
+ def reference_search_roots(kind: str, workspace: Path, output_root: Path | None = None) -> tuple[Path, ...]:
55
+ """Return deterministic search roots used by resolution and completion."""
56
+ roots: list[Path] = []
57
+ for name in _RESOURCE_DIRS.get(kind, ()):
58
+ roots.extend((workspace / ".aria" / name, workspace / name))
59
+ if output_root:
60
+ roots.append(output_root / name)
61
+ return tuple(dict.fromkeys(roots))
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class ReferencePolicy:
66
+ workspace: Path = field(default_factory=Path.cwd)
67
+ output_root: Path | None = None
68
+ allowed_roots: tuple[Path, ...] = ()
69
+
70
+ def normalized(self) -> "ReferencePolicy":
71
+ workspace = self.workspace.expanduser().resolve()
72
+ output_root = self.output_root.expanduser().resolve() if self.output_root else None
73
+ roots = [workspace]
74
+ if output_root:
75
+ roots.append(output_root)
76
+ roots.extend(path.expanduser().resolve() for path in self.allowed_roots)
77
+ return ReferencePolicy(
78
+ workspace=workspace,
79
+ output_root=output_root,
80
+ allowed_roots=tuple(dict.fromkeys(roots)),
81
+ )
82
+
83
+
84
+ @dataclass(frozen=True)
85
+ class ContextReference:
86
+ raw: str
87
+ kind: str
88
+ value: str
89
+ start: int
90
+ end: int
91
+ resolved_value: str = ""
92
+ path: Path | None = None
93
+ error: str = ""
94
+
95
+ @property
96
+ def ok(self) -> bool:
97
+ return not self.error
98
+
99
+
100
+ @dataclass(frozen=True)
101
+ class PreparedReferences:
102
+ original_text: str
103
+ expanded_text: str
104
+ context_block: str
105
+ references: tuple[ContextReference, ...]
106
+
107
+ @property
108
+ def errors(self) -> tuple[ContextReference, ...]:
109
+ return tuple(ref for ref in self.references if ref.error)
110
+
111
+ @property
112
+ def prompt(self) -> str:
113
+ if not self.context_block:
114
+ return self.expanded_text
115
+ return f"{self.expanded_text}\n\n{self.context_block}"
116
+
117
+
118
+ def iter_reference_tokens(text: str) -> Iterator[tuple[str, str, str, int, int]]:
119
+ """Yield ``(raw, kind, value, start, end)`` while ignoring email addresses."""
120
+ for match in _REF_RE.finditer(text):
121
+ kind = (match.group("kind") or "file").lower()
122
+ token = match.group("value")
123
+ end = match.end()
124
+ if token[:1] in {"\"", "'"} and token[-1:] == token[:1]:
125
+ value = token[1:-1]
126
+ else:
127
+ trailing = _TRAILING_PUNCTUATION + ("." if kind not in {"file", "folder"} else "")
128
+ value = token.rstrip(trailing)
129
+ end -= len(token) - len(value)
130
+ if value:
131
+ yield text[match.start():end], kind, value, match.start(), end
132
+
133
+
134
+ class ReferenceService:
135
+ """Resolve and prepare explicit context references without side effects."""
136
+
137
+ def __init__(self, policy: ReferencePolicy | None = None):
138
+ self.policy = (policy or ReferencePolicy()).normalized()
139
+
140
+ def prepare(self, text: str) -> PreparedReferences:
141
+ refs = tuple(self._resolve(*token) for token in iter_reference_tokens(text))
142
+ expanded = text
143
+ for ref in reversed(refs):
144
+ if not ref.ok:
145
+ continue
146
+ replacement = self._replacement(ref)
147
+ expanded = expanded[:ref.start] + replacement + expanded[ref.end:]
148
+
149
+ blocks: list[str] = []
150
+ for ref in refs:
151
+ if not ref.ok:
152
+ continue
153
+ target = str(ref.path) if ref.path is not None else ref.resolved_value
154
+ if ref.kind == "folder":
155
+ action = "Inspect on demand with list_files or search_code."
156
+ elif ref.kind == "asset":
157
+ action = "Fetch on demand with get_market_data or get_market_history."
158
+ else:
159
+ action = "Inspect on demand with read_file or analyze_file."
160
+ blocks.append(f"- {ref.kind}: {target}\n {action}")
161
+
162
+ context_block = ""
163
+ if blocks:
164
+ context_block = (
165
+ "[Aria resource references - pointers only]\n"
166
+ "No resource content is preloaded. Use the named tools before answering.\n"
167
+ + "\n".join(blocks)
168
+ + "\n[End Aria resource references]"
169
+ )
170
+ return PreparedReferences(text, expanded, context_block, refs)
171
+
172
+ def _resolve(self, raw: str, kind: str, value: str, start: int, end: int) -> ContextReference:
173
+ if kind not in _KIND_MAP:
174
+ valid = ", ".join(item.name for item in REFERENCE_KINDS)
175
+ return ContextReference(raw, kind, value, start, end, error=f"unknown reference type '{kind}' (use: {valid})")
176
+ if kind == "asset":
177
+ if not _ASSET_RE.fullmatch(value):
178
+ return ContextReference(raw, kind, value, start, end, error=f"invalid market asset '{value}'")
179
+ return ContextReference(raw, kind, value, start, end, resolved_value=value.upper())
180
+
181
+ path = self._resolve_path(kind, value)
182
+ if path is None:
183
+ return ContextReference(raw, kind, value, start, end, error=f"{kind} not found: {value}")
184
+ if not self._is_allowed(path):
185
+ return ContextReference(raw, kind, value, start, end, path=path, error=f"reference is outside the allowed workspace: {value}")
186
+ if kind == "folder" and not path.is_dir():
187
+ return ContextReference(raw, kind, value, start, end, path=path, error=f"folder not found: {value}")
188
+ if kind != "folder" and not path.is_file():
189
+ return ContextReference(raw, kind, value, start, end, path=path, error=f"{kind} is not a file: {value}")
190
+
191
+ return ContextReference(
192
+ raw, kind, value, start, end,
193
+ resolved_value=str(path), path=path,
194
+ )
195
+
196
+ def _resolve_path(self, kind: str, value: str) -> Path | None:
197
+ if kind in {"file", "folder"}:
198
+ candidate = Path(os.path.expandvars(value)).expanduser()
199
+ if not candidate.is_absolute():
200
+ candidate = self.policy.workspace / candidate
201
+ try:
202
+ return candidate.resolve()
203
+ except OSError:
204
+ return None
205
+
206
+ for root in self._resource_roots(kind):
207
+ for suffix in _RESOURCE_EXTENSIONS:
208
+ candidate = root / f"{value}{suffix}"
209
+ if candidate.exists():
210
+ return candidate.resolve()
211
+ if not root.is_dir():
212
+ continue
213
+ target_names = {value.lower(), *(f"{value}{suffix}".lower() for suffix in _RESOURCE_EXTENSIONS if suffix)}
214
+ seen = 0
215
+ for candidate in root.rglob("*"):
216
+ seen += 1
217
+ if seen > 2_000:
218
+ break
219
+ if candidate.is_file() and (candidate.name.lower() in target_names or candidate.stem.lower() == value.lower()):
220
+ return candidate.resolve()
221
+ return None
222
+
223
+ def _resource_roots(self, kind: str) -> Iterable[Path]:
224
+ yield from reference_search_roots(kind, self.policy.workspace, self.policy.output_root)
225
+
226
+ def _is_allowed(self, path: Path) -> bool:
227
+ resolved = path.resolve()
228
+ return any(resolved == root or root in resolved.parents for root in self.policy.allowed_roots)
229
+
230
+ def _replacement(self, ref: ContextReference) -> str:
231
+ if ref.kind == "asset":
232
+ return ref.resolved_value
233
+ if ref.path is not None:
234
+ return shlex.quote(str(ref.path)) if " " in str(ref.path) else str(ref.path)
235
+ return ref.resolved_value
236
+
237
+ def build_reference_service(
238
+ *,
239
+ workspace: Path | None = None,
240
+ output_root: Path | None = None,
241
+ allowed_roots: Sequence[Path] = (),
242
+ ) -> ReferenceService:
243
+ return ReferenceService(ReferencePolicy(
244
+ workspace=workspace or Path.cwd(),
245
+ output_root=output_root,
246
+ allowed_roots=tuple(allowed_roots),
247
+ ))