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,135 @@
1
+ """Local market symbol metadata used to normalize provider output.
2
+
3
+ Providers occasionally omit display names or return a generic/default currency.
4
+ This module keeps deterministic exchange metadata close to the CLI so prompt
5
+ injection and tool responses do not ask the model to guess symbol identity.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from typing import Any, Mapping
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class MarketAssetMeta:
15
+ symbol: str
16
+ short_label: str
17
+ display_name: str
18
+ currency: str
19
+ exchange: str = ""
20
+
21
+
22
+ _SYMBOL_META: dict[str, MarketAssetMeta] = {
23
+ "MC.PA": MarketAssetMeta(
24
+ symbol="MC.PA",
25
+ short_label="LVMH/路易威登",
26
+ display_name="LVMH Moet Hennessy Louis Vuitton SE",
27
+ currency="EUR",
28
+ exchange="Euronext Paris",
29
+ ),
30
+ "RMS.PA": MarketAssetMeta(
31
+ symbol="RMS.PA",
32
+ short_label="Hermes/爱马仕",
33
+ display_name="Hermes International SCA",
34
+ currency="EUR",
35
+ exchange="Euronext Paris",
36
+ ),
37
+ "KER.PA": MarketAssetMeta(
38
+ symbol="KER.PA",
39
+ short_label="Kering/开云集团",
40
+ display_name="Kering SA",
41
+ currency="EUR",
42
+ exchange="Euronext Paris",
43
+ ),
44
+ }
45
+
46
+ _SUFFIX_CURRENCY: tuple[tuple[str, str], ...] = (
47
+ (".SS", "CNY"),
48
+ (".SH", "CNY"),
49
+ (".SZ", "CNY"),
50
+ (".HK", "HKD"),
51
+ (".PA", "EUR"),
52
+ (".DE", "EUR"),
53
+ (".MI", "EUR"),
54
+ (".AS", "EUR"),
55
+ (".BR", "EUR"),
56
+ (".MC", "EUR"),
57
+ (".LS", "EUR"),
58
+ (".SW", "CHF"),
59
+ (".L", "GBp"),
60
+ (".TO", "CAD"),
61
+ (".AX", "AUD"),
62
+ (".T", "JPY"),
63
+ )
64
+
65
+
66
+ def normalize_market_symbol(symbol: str) -> str:
67
+ return str(symbol or "").strip().upper()
68
+
69
+
70
+ def market_asset_meta(symbol: str) -> MarketAssetMeta | None:
71
+ return _SYMBOL_META.get(normalize_market_symbol(symbol))
72
+
73
+
74
+ def default_currency_for_symbol(symbol: str) -> str:
75
+ normalized = normalize_market_symbol(symbol)
76
+ meta = market_asset_meta(normalized)
77
+ if meta:
78
+ return meta.currency
79
+ for suffix, currency in _SUFFIX_CURRENCY:
80
+ if normalized.endswith(suffix):
81
+ return currency
82
+ return ""
83
+
84
+
85
+ def _provider_currency_looks_wrong(symbol: str, provider_currency: Any, inferred_currency: str) -> bool:
86
+ if not inferred_currency:
87
+ return False
88
+ currency = str(provider_currency or "").strip().upper()
89
+ inferred = inferred_currency.upper()
90
+ if not currency:
91
+ return True
92
+ if currency == inferred:
93
+ return False
94
+ normalized = normalize_market_symbol(symbol)
95
+ # Most wrong cases here come from global fallbacks defaulting non-US symbols
96
+ # to USD. Do not override real US tickers that have no exchange suffix.
97
+ return currency == "USD" and "." in normalized
98
+
99
+
100
+ def enrich_market_quote(symbol: str, quote: Mapping[str, Any] | None) -> dict[str, Any]:
101
+ """Return a quote copy with deterministic symbol name/currency metadata."""
102
+ data = dict(quote or {})
103
+ normalized = normalize_market_symbol(symbol or data.get("symbol") or "")
104
+ if normalized and not data.get("symbol"):
105
+ data["symbol"] = normalized
106
+
107
+ meta = market_asset_meta(normalized)
108
+ inferred_currency = default_currency_for_symbol(normalized)
109
+ if _provider_currency_looks_wrong(normalized, data.get("currency"), inferred_currency):
110
+ data["currency"] = inferred_currency
111
+ data["currency_source"] = "symbol_metadata"
112
+ elif not data.get("currency") and inferred_currency:
113
+ data["currency"] = inferred_currency
114
+ data["currency_source"] = "symbol_metadata"
115
+
116
+ if meta:
117
+ current_name = str(data.get("name") or "").strip()
118
+ if not current_name or current_name.upper() == normalized:
119
+ data["name"] = meta.display_name
120
+ data.setdefault("display_name", meta.display_name)
121
+ data.setdefault("short_label", meta.short_label)
122
+ data.setdefault("exchange", meta.exchange)
123
+ return data
124
+
125
+
126
+ def market_display_label(symbol: str, quote: Mapping[str, Any] | None = None) -> str:
127
+ data = dict(quote or {})
128
+ normalized = normalize_market_symbol(symbol or data.get("symbol") or "")
129
+ meta = market_asset_meta(normalized)
130
+ if meta:
131
+ return f"{meta.short_label}({normalized})"
132
+ name = str(data.get("name") or data.get("display_name") or "").strip()
133
+ if name and name.upper() != normalized:
134
+ return f"{name}({normalized})"
135
+ return normalized or name or "market_data"
@@ -0,0 +1,487 @@
1
+ """Market symbol resolution across A-shares, HK, global tickers, FX and futures.
2
+
3
+ The resolver is intentionally cache-first. Static aliases cover common global
4
+ assets and critical names; optional akshare loaders populate full A-share/HK
5
+ name tables into a local cache when available.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ import json
11
+ import os
12
+ from pathlib import Path
13
+ import re
14
+ import time
15
+ from typing import Callable, Iterable
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class MarketSymbol:
20
+ name: str
21
+ symbol: str
22
+ market: str
23
+ source: str = "static"
24
+
25
+
26
+ STATIC_MARKET_ALIASES: dict[str, MarketSymbol] = {
27
+ # A-share fixes and high-frequency names not guaranteed in older static maps
28
+ "斯迪克": MarketSymbol("斯迪克", "300806", "CN", "static"),
29
+ "四环生物": MarketSymbol("四环生物", "000518", "CN", "static"),
30
+ # China / HK indices
31
+ "上证指数": MarketSymbol("上证指数", "000001.SS", "INDEX", "static"),
32
+ "上证": MarketSymbol("上证指数", "000001.SS", "INDEX", "static"),
33
+ "深证成指": MarketSymbol("深证成指", "399001.SZ", "INDEX", "static"),
34
+ "创业板指": MarketSymbol("创业板指", "399006.SZ", "INDEX", "static"),
35
+ "沪深300": MarketSymbol("沪深300", "000300.SS", "INDEX", "static"),
36
+ "中证500": MarketSymbol("中证500", "000905.SS", "INDEX", "static"),
37
+ "恒生指数": MarketSymbol("恒生指数", "^HSI", "INDEX", "static"),
38
+ "恒指": MarketSymbol("恒生指数", "^HSI", "INDEX", "static"),
39
+ # US/global indices
40
+ "标普500": MarketSymbol("标普500", "^GSPC", "INDEX", "static"),
41
+ "标普": MarketSymbol("标普500", "^GSPC", "INDEX", "static"),
42
+ "纳斯达克": MarketSymbol("纳斯达克综合", "^IXIC", "INDEX", "static"),
43
+ "纳指": MarketSymbol("纳斯达克综合", "^IXIC", "INDEX", "static"),
44
+ "道琼斯": MarketSymbol("道琼斯工业指数", "^DJI", "INDEX", "static"),
45
+ "道指": MarketSymbol("道琼斯工业指数", "^DJI", "INDEX", "static"),
46
+ "罗素2000": MarketSymbol("罗素2000", "^RUT", "INDEX", "static"),
47
+ "恐慌指数": MarketSymbol("VIX", "^VIX", "INDEX", "static"),
48
+ "vix": MarketSymbol("VIX", "^VIX", "INDEX", "static"),
49
+ "富时100": MarketSymbol("FTSE 100", "^FTSE", "INDEX", "static"),
50
+ "德国dax": MarketSymbol("DAX", "^GDAXI", "INDEX", "static"),
51
+ "dax": MarketSymbol("DAX", "^GDAXI", "INDEX", "static"),
52
+ "法国cac": MarketSymbol("CAC 40", "^FCHI", "INDEX", "static"),
53
+ "日经225": MarketSymbol("Nikkei 225", "^N225", "INDEX", "static"),
54
+ # Europe equities / brands frequently asked by name rather than ticker.
55
+ "lvmh": MarketSymbol("LVMH Moet Hennessy Louis Vuitton SE", "MC.PA", "EU", "static"),
56
+ "路易威登": MarketSymbol("LVMH Moet Hennessy Louis Vuitton SE", "MC.PA", "EU", "static"),
57
+ "路易斯威登": MarketSymbol("LVMH Moet Hennessy Louis Vuitton SE", "MC.PA", "EU", "static"),
58
+ "louis vuitton": MarketSymbol("LVMH Moet Hennessy Louis Vuitton SE", "MC.PA", "EU", "static"),
59
+ "爱马仕": MarketSymbol("Hermes International SCA", "RMS.PA", "EU", "static"),
60
+ "开云集团": MarketSymbol("Kering SA", "KER.PA", "EU", "static"),
61
+ "古驰": MarketSymbol("Kering SA", "KER.PA", "EU", "static"),
62
+ # Crypto
63
+ "比特币": MarketSymbol("比特币", "BTC-USD", "CRYPTO", "static"),
64
+ "btc": MarketSymbol("Bitcoin", "BTC-USD", "CRYPTO", "static"),
65
+ "以太坊": MarketSymbol("以太坊", "ETH-USD", "CRYPTO", "static"),
66
+ "eth": MarketSymbol("Ethereum", "ETH-USD", "CRYPTO", "static"),
67
+ "狗狗币": MarketSymbol("Dogecoin", "DOGE-USD", "CRYPTO", "static"),
68
+ "sol": MarketSymbol("Solana", "SOL-USD", "CRYPTO", "static"),
69
+ "索拉纳": MarketSymbol("Solana", "SOL-USD", "CRYPTO", "static"),
70
+ # FX
71
+ "美元人民币": MarketSymbol("USD/CNY", "CNY=X", "FX", "static"),
72
+ "人民币汇率": MarketSymbol("USD/CNY", "CNY=X", "FX", "static"),
73
+ "美元兑人民币": MarketSymbol("USD/CNY", "CNY=X", "FX", "static"),
74
+ "美元指数": MarketSymbol("美元指数", "DX-Y.NYB", "FX", "static"),
75
+ "欧元美元": MarketSymbol("EUR/USD", "EURUSD=X", "FX", "static"),
76
+ "欧元兑美元": MarketSymbol("EUR/USD", "EURUSD=X", "FX", "static"),
77
+ "美元日元": MarketSymbol("USD/JPY", "JPY=X", "FX", "static"),
78
+ "英镑美元": MarketSymbol("GBP/USD", "GBPUSD=X", "FX", "static"),
79
+ # Futures / commodities via Yahoo continuous futures
80
+ "黄金": MarketSymbol("黄金期货", "GC=F", "FUTURES", "static"),
81
+ "白银": MarketSymbol("白银期货", "SI=F", "FUTURES", "static"),
82
+ "原油": MarketSymbol("WTI原油期货", "CL=F", "FUTURES", "static"),
83
+ "wti": MarketSymbol("WTI原油期货", "CL=F", "FUTURES", "static"),
84
+ "布伦特": MarketSymbol("布伦特原油期货", "BZ=F", "FUTURES", "static"),
85
+ "铜": MarketSymbol("铜期货", "HG=F", "FUTURES", "static"),
86
+ "天然气": MarketSymbol("天然气期货", "NG=F", "FUTURES", "static"),
87
+ "玉米": MarketSymbol("玉米期货", "ZC=F", "FUTURES", "static"),
88
+ "大豆": MarketSymbol("大豆期货", "ZS=F", "FUTURES", "static"),
89
+ }
90
+
91
+
92
+ def _cache_path() -> Path:
93
+ root = Path(os.getenv("ARIA_CACHE_DIR") or (Path.home() / ".aria" / "cache"))
94
+ return root / "market_universe.json"
95
+
96
+
97
+ def _load_cache(path: Path | None = None, *, max_age_seconds: int = 7 * 86400) -> list[MarketSymbol]:
98
+ path = path or _cache_path()
99
+ try:
100
+ if not path.exists() or time.time() - path.stat().st_mtime > max_age_seconds:
101
+ return []
102
+ payload = json.loads(path.read_text(encoding="utf-8"))
103
+ return [
104
+ MarketSymbol(
105
+ name=str(item.get("name") or ""),
106
+ symbol=str(item.get("symbol") or ""),
107
+ market=str(item.get("market") or ""),
108
+ source=str(item.get("source") or "cache"),
109
+ )
110
+ for item in payload.get("symbols", [])
111
+ if item.get("name") and item.get("symbol")
112
+ ]
113
+ except Exception:
114
+ return []
115
+
116
+
117
+ def _write_cache(symbols: Iterable[MarketSymbol], path: Path | None = None) -> None:
118
+ path = path or _cache_path()
119
+ try:
120
+ path.parent.mkdir(parents=True, exist_ok=True)
121
+ data = {
122
+ "updated_at": int(time.time()),
123
+ "symbols": [s.__dict__ for s in symbols if s.name and s.symbol],
124
+ }
125
+ path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
126
+ except Exception:
127
+ pass
128
+
129
+
130
+ def _iter_static_symbols() -> list[MarketSymbol]:
131
+ seen: set[tuple[str, str]] = set()
132
+ out: list[MarketSymbol] = []
133
+ for item in STATIC_MARKET_ALIASES.values():
134
+ key = (item.name, item.symbol)
135
+ if key not in seen:
136
+ out.append(item)
137
+ seen.add(key)
138
+ return out
139
+
140
+
141
+ def _symbols_from_frame(frame, *, name_cols: tuple[str, ...], code_cols: tuple[str, ...], market: str, source: str) -> list[MarketSymbol]:
142
+ out: list[MarketSymbol] = []
143
+ try:
144
+ columns = {str(c).lower(): c for c in frame.columns}
145
+ name_col = next((columns[c.lower()] for c in name_cols if c.lower() in columns), None)
146
+ code_col = next((columns[c.lower()] for c in code_cols if c.lower() in columns), None)
147
+ if name_col is None or code_col is None:
148
+ return []
149
+ for _, row in frame.iterrows():
150
+ name = str(row.get(name_col, "")).strip()
151
+ code = str(row.get(code_col, "")).strip().upper()
152
+ if not name or not code or code.lower() == "nan":
153
+ continue
154
+ if market == "HK":
155
+ digits = re.sub(r"\D", "", code)
156
+ if digits:
157
+ code = f"{digits.zfill(4)}.HK"
158
+ out.append(MarketSymbol(name, code, market, source))
159
+ except Exception:
160
+ return []
161
+ return out
162
+
163
+
164
+ def is_offline() -> bool:
165
+ """True 时跳过一切联网抓取,只用静态符号表。
166
+
167
+ 2026-08-19:fetch_market_universe() 会真的打 akshare(stock_info_a_code_name
168
+ 与 stock_hk_spot_em),既没有超时也没有开关。CI 上这两个请求会挂住,
169
+ tests/test_aria_cli_core.py::test_send_message_visual_route_helper 因此撞
170
+ pytest-timeout 的 60 秒上限而失败——测的明明是"自然语言路由到 /chart"这种
171
+ 纯本地逻辑,答案(Apple→AAPL)来自 market_detect.py 的静态映射,根本不需要
172
+ 联网,网络只是扫描全量宇宙时的副作用。
173
+
174
+ 默认行为不变(有网就抓)。设 ARIA_OFFLINE=1 即可让它立刻退回静态表,
175
+ 供 CI、离线环境和确定性测试使用。
176
+ """
177
+ return os.getenv("ARIA_OFFLINE", "").strip().lower() in {"1", "true", "yes", "on"}
178
+
179
+
180
+ def _fetch_timeout_seconds() -> float:
181
+ """Wall-clock budget for one remote universe fetch.
182
+
183
+ ``is_offline()`` documents that these akshare calls had neither a timeout
184
+ nor a switch, so a slow or hanging endpoint stalled the caller
185
+ indefinitely — on the routing hot path that reads as the CLI freezing.
186
+ ARIA_OFFLINE=1 still skips the network entirely; this bounds the wait when
187
+ the network *is* used.
188
+ """
189
+ try:
190
+ value = float(os.getenv("ARIA_UNIVERSE_FETCH_TIMEOUT", "8") or 8)
191
+ except (TypeError, ValueError):
192
+ return 8.0
193
+ return max(1.0, value)
194
+
195
+
196
+ def _call_with_timeout(fn, timeout: float):
197
+ """Run *fn* with a wall-clock budget; return None if it overruns.
198
+
199
+ The worker thread is a daemon and is abandoned on timeout — akshare offers
200
+ no cancellation — but the caller is released instead of blocking forever.
201
+ """
202
+ import concurrent.futures
203
+
204
+ executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
205
+ try:
206
+ future = executor.submit(fn)
207
+ try:
208
+ return future.result(timeout=timeout)
209
+ except Exception:
210
+ return None
211
+ finally:
212
+ executor.shutdown(wait=False)
213
+
214
+
215
+ def fetch_market_universe() -> list[MarketSymbol]:
216
+ """Fetch A-share and HK symbol tables when akshare is available."""
217
+ symbols = _iter_static_symbols()
218
+ if is_offline():
219
+ return symbols
220
+ budget = _fetch_timeout_seconds()
221
+ try:
222
+ import akshare as ak
223
+ try:
224
+ a_df = _call_with_timeout(ak.stock_info_a_code_name, budget)
225
+ if a_df is None:
226
+ raise TimeoutError("stock_info_a_code_name timed out")
227
+ symbols.extend(_symbols_from_frame(
228
+ a_df,
229
+ name_cols=("name", "证券简称", "股票简称", "名称"),
230
+ code_cols=("code", "证券代码", "股票代码", "代码"),
231
+ market="CN",
232
+ source="akshare:a_code_name",
233
+ ))
234
+ except Exception:
235
+ pass
236
+ try:
237
+ hk_df = _call_with_timeout(ak.stock_hk_spot_em, budget)
238
+ if hk_df is None:
239
+ raise TimeoutError("stock_hk_spot_em timed out")
240
+ symbols.extend(_symbols_from_frame(
241
+ hk_df,
242
+ name_cols=("名称", "股票简称", "name"),
243
+ code_cols=("代码", "code", "股票代码"),
244
+ market="HK",
245
+ source="akshare:hk_spot",
246
+ ))
247
+ except Exception:
248
+ pass
249
+ except Exception:
250
+ pass
251
+
252
+ dedup: dict[tuple[str, str], MarketSymbol] = {}
253
+ for item in symbols:
254
+ dedup[(item.name.lower(), item.symbol.upper())] = item
255
+ return list(dedup.values())
256
+
257
+
258
+ # Guard against repeating an expensive remote refresh within one process. The
259
+ # fetch pulls the full A-share + HK symbol universe over the network; when it
260
+ # fails or returns nothing, retrying it on the next message just stalls the REPL
261
+ # again for the same result.
262
+ _UNIVERSE_REFRESH_ATTEMPTED = False
263
+
264
+
265
+ def _universe_refresh_attempted() -> bool:
266
+ return _UNIVERSE_REFRESH_ATTEMPTED
267
+
268
+
269
+ def _mark_universe_refresh_attempted() -> None:
270
+ global _UNIVERSE_REFRESH_ATTEMPTED
271
+ _UNIVERSE_REFRESH_ATTEMPTED = True
272
+
273
+
274
+ def reset_universe_refresh_guard() -> None:
275
+ """Clear the once-per-process refresh guard (tests, explicit /refresh)."""
276
+ global _UNIVERSE_REFRESH_ATTEMPTED
277
+ _UNIVERSE_REFRESH_ATTEMPTED = False
278
+
279
+
280
+ def ensure_market_universe(*, force: bool = False) -> list[MarketSymbol]:
281
+ cached = [] if force else _load_cache()
282
+ if cached:
283
+ return _iter_static_symbols() + cached
284
+ _mark_universe_refresh_attempted()
285
+ fetched = fetch_market_universe()
286
+ _write_cache(fetched)
287
+ return fetched
288
+
289
+
290
+ def _find_mention(low_text: str, needle: str) -> int:
291
+ """Position of *needle* in lowercased text, or -1.
292
+
293
+ ASCII needles must sit on word boundaries — plain substring matching
294
+ turned "whether" into an ETH hit and "gateway" into GE (observed in the
295
+ channels e2e drill). CJK names keep substring semantics: Chinese has no
296
+ word boundaries, which is why find() was used originally.
297
+ """
298
+ n = str(needle or "").lower()
299
+ if not n:
300
+ return -1
301
+ if re.fullmatch(r"[a-z0-9.\-]+", n):
302
+ m = re.search(rf"(?<![a-z0-9]){re.escape(n)}(?![a-z0-9])", low_text)
303
+ return m.start() if m else -1
304
+ return low_text.find(n)
305
+
306
+
307
+ def resolve_market_mentions(
308
+ text: str,
309
+ *,
310
+ limit: int = 6,
311
+ load_universe: Callable[[], list[MarketSymbol]] | None = None,
312
+ ) -> list[tuple[int, MarketSymbol]]:
313
+ """Resolve named market assets mentioned in text, preserving positions."""
314
+ if not text:
315
+ return []
316
+ low = text.lower()
317
+ hits: list[tuple[int, MarketSymbol]] = []
318
+ for alias, item in sorted(STATIC_MARKET_ALIASES.items(), key=lambda kv: -len(kv[0])):
319
+ idx = _find_mention(low, alias)
320
+ if idx >= 0:
321
+ hits.append((idx, item))
322
+
323
+ def scan(items: Iterable[MarketSymbol]) -> None:
324
+ for item in sorted(items, key=lambda s: -len(s.name)):
325
+ if not item.name:
326
+ continue
327
+ idx = _find_mention(low, item.name)
328
+ if idx >= 0:
329
+ hits.append((idx, item))
330
+
331
+ if load_universe is not None:
332
+ scan(load_universe())
333
+ else:
334
+ scan(_load_cache())
335
+ market_words = "走势|预测|股价|股票|行情|趋势|技术面|基本面|涨跌|价格|市值|k线|图表|财报"
336
+ if (
337
+ not hits
338
+ and re.search(r"[\u4e00-\u9fff]", text)
339
+ and re.search(market_words, text, re.I)
340
+ and not _universe_refresh_attempted()
341
+ ):
342
+ # Refresh only when the local cache cannot answer, and only once per
343
+ # process. This used to pass force=True, which skipped the 7-day
344
+ # cache and re-downloaded the entire A-share + HK universe over the
345
+ # network on *every* message containing a word like "行情" — a
346
+ # multi-second synchronous stall on the routing hot path, repeated
347
+ # even for messages such as "现在的行情呢" that name no company
348
+ # at all and so can never be resolved by any refresh.
349
+ _mark_universe_refresh_attempted()
350
+ scan(ensure_market_universe())
351
+
352
+ ordered: list[tuple[int, MarketSymbol]] = []
353
+ seen_symbols: set[str] = set()
354
+ for idx, item in sorted(hits, key=lambda pair: (pair[0], -len(pair[1].name))):
355
+ sym = item.symbol.upper()
356
+ if sym in seen_symbols:
357
+ continue
358
+ ordered.append((idx, item))
359
+ seen_symbols.add(sym)
360
+ if len(ordered) >= limit:
361
+ break
362
+ return ordered
363
+
364
+
365
+ def ambiguous_market_candidates(
366
+ text: str,
367
+ *,
368
+ limit: int = 20,
369
+ load_universe: Callable[[], list[MarketSymbol]] | None = None,
370
+ ) -> list[tuple[int, str, list[MarketSymbol]]]:
371
+ """Return same-name mentions that map to more than one market symbol.
372
+
373
+ Different assets mentioned in one sentence are not considered ambiguous;
374
+ candidates must start at the same position and have the same display name.
375
+ """
376
+ # Ambiguity checks run on every natural-language message, so they must stay
377
+ # cache-only. A missing cache must not turn an ordinary methodology
378
+ # question into a slow network refresh.
379
+ loader = load_universe or _load_cache
380
+ hits = resolve_market_mentions(text, limit=limit, load_universe=loader)
381
+ return _ambiguous_groups_from_hits(hits)
382
+
383
+
384
+ def _ambiguous_groups_from_hits(
385
+ hits: Iterable[tuple[int, MarketSymbol]],
386
+ ) -> list[tuple[int, str, list[MarketSymbol]]]:
387
+ grouped: dict[tuple[int, str], list[MarketSymbol]] = {}
388
+ display_names: dict[tuple[int, str], str] = {}
389
+ for position, item in hits:
390
+ normalized_name = re.sub(r"\s+", "", item.name).casefold()
391
+ key = (position, normalized_name)
392
+ grouped.setdefault(key, []).append(item)
393
+ display_names[key] = item.name
394
+
395
+ ambiguous: list[tuple[int, str, list[MarketSymbol]]] = []
396
+ for key, candidates in grouped.items():
397
+ unique: list[MarketSymbol] = []
398
+ seen: set[str] = set()
399
+ for candidate in candidates:
400
+ symbol = candidate.symbol.upper()
401
+ if symbol not in seen:
402
+ unique.append(candidate)
403
+ seen.add(symbol)
404
+ if len(unique) > 1:
405
+ ambiguous.append((key[0], display_names[key], unique))
406
+ ambiguous.sort(key=lambda item: item[0])
407
+ return ambiguous
408
+
409
+
410
+ def select_market_candidate(reply: str, candidates: Iterable[MarketSymbol]) -> MarketSymbol | None:
411
+ """Resolve a short clarification reply to one of the offered candidates."""
412
+ options = list(candidates)
413
+ text = str(reply or "").strip()
414
+ if not text:
415
+ return None
416
+ normalized = re.sub(r"\s+", "", text).casefold()
417
+ for option in options:
418
+ market_aliases = {
419
+ "CN": {"a股", "沪深", "中国a股"},
420
+ "HK": {"港股", "香港"},
421
+ "US": {"美股", "美国"},
422
+ "EU": {"欧股", "欧洲"},
423
+ }.get(option.market.upper(), set())
424
+ labels = {
425
+ option.symbol,
426
+ option.name,
427
+ option.market,
428
+ f"{option.name}{option.symbol}",
429
+ f"{option.name}{option.market}",
430
+ *market_aliases,
431
+ }
432
+ if normalized in {re.sub(r"\s+", "", value).casefold() for value in labels if value}:
433
+ return option
434
+ if text.isdigit():
435
+ index = int(text) - 1
436
+ return options[index] if 0 <= index < len(options) else None
437
+ return None
438
+
439
+
440
+ def resolve_market_symbol(text: str) -> str:
441
+ hits = resolve_market_mentions(text, limit=20)
442
+ if _ambiguous_groups_from_hits(hits):
443
+ return ""
444
+ return hits[0][1].symbol if hits else ""
445
+
446
+
447
+ # Words that routinely sit in front of a market term without naming a company:
448
+ # time adverbs, demonstratives and scope words. Without this stoplist the guard
449
+ # below read "现在的行情呢" as the unknown company "现在", which blocked the very
450
+ # follow-up case it exists to support — "现在的股票和趋势呢" is the example named
451
+ # in _is_market_snapshot_request's own docstring, and it did not work.
452
+ _NON_COMPANY_QUALIFIERS = (
453
+ "现在", "目前", "当前", "今天", "今日", "昨天", "昨日", "明天", "近期",
454
+ "最新", "最近", "短期", "中期", "长期", "未来", "后续", "以上", "上面",
455
+ "刚才", "这个", "那个", "这些", "那些", "整体", "总体", "大盘", "所有",
456
+ "上述", "全部", "其他", "别的", "同类", "相关", "本周", "本月", "今年",
457
+ "去年", "它", "他", "她", "我", "你",
458
+ )
459
+
460
+
461
+ def looks_like_unresolved_market_name(text: str) -> bool:
462
+ """Heuristic guard: a Chinese name before market words should not inherit history."""
463
+ if resolve_market_symbol(text):
464
+ return False
465
+ if not re.search(r"[\u4e00-\u9fff]{2,12}", text or ""):
466
+ return False
467
+ market_words = "走势|预测|股价|股票|行情|趋势|技术面|基本面|涨跌|价格|市值"
468
+ # A market term can also precede another one ("股价涨跌怎么样"); such a
469
+ # qualifier names no company either and must be stripped alongside the
470
+ # generic time/scope words.
471
+ droppable = tuple(market_words.split("|")) + _NON_COMPANY_QUALIFIERS
472
+ pattern = rf"([\u4e00-\u9fffA-Za-z0-9]{{2,16}}?)(?:的)?(?:{market_words})"
473
+ for match in re.finditer(pattern, text or ""):
474
+ qualifier = match.group(1)
475
+ # Strip trailing non-company words so "腾讯现在的股价" still reports the
476
+ # name "腾讯", while "现在的股价" and "股价涨跌" report nothing.
477
+ changed = True
478
+ while changed and qualifier:
479
+ changed = False
480
+ for word in droppable:
481
+ if qualifier.endswith(word):
482
+ qualifier = qualifier[: -len(word)]
483
+ changed = True
484
+ break
485
+ if len(qualifier) >= 2:
486
+ return True
487
+ return False