aria-code 0.167.0__py3-none-win_amd64.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 (601) hide show
  1. aria_bots/__init__.py +7 -0
  2. aria_bots/channels/README.md +97 -0
  3. aria_bots/channels/__init__.py +1 -0
  4. aria_bots/channels/approvals.py +263 -0
  5. aria_bots/channels/conversation.py +280 -0
  6. aria_bots/channels/digest.py +186 -0
  7. aria_bots/channels/registry.py +95 -0
  8. aria_bots/engine.py +144 -0
  9. aria_bots/feishu_bot.py +1385 -0
  10. aria_bots/logistics/__init__.py +1 -0
  11. aria_bots/logistics/carriers.py +253 -0
  12. aria_bots/logistics/freight.py +154 -0
  13. aria_bots/logistics/inventory.py +242 -0
  14. aria_bots/logistics/mcp_server.py +156 -0
  15. aria_bots/logistics/records.py +119 -0
  16. aria_bots/logistics/tenancy.py +152 -0
  17. aria_bots/market.py +91 -0
  18. aria_bots/relay_client.py +326 -0
  19. aria_bots/telegram_bot.py +299 -0
  20. aria_code/__init__.py +111 -0
  21. aria_code/_bin/aria-native.exe +0 -0
  22. aria_code/_bin/aria-runtime.exe +0 -0
  23. aria_code/_version.py +20 -0
  24. aria_code/account_memory.py +296 -0
  25. aria_code/account_session.py +166 -0
  26. aria_code/adk_apps/__init__.py +1 -0
  27. aria_code/adk_apps/aria_code_review/__init__.py +1 -0
  28. aria_code/adk_apps/aria_code_review/agent.py +32 -0
  29. aria_code/adk_apps/aria_research/__init__.py +1 -0
  30. aria_code/adk_apps/aria_research/agent.py +31 -0
  31. aria_code/agents/__init__.py +49 -0
  32. aria_code/agents/base.py +329 -0
  33. aria_code/agents/code_review.py +200 -0
  34. aria_code/agents/deep/__init__.py +37 -0
  35. aria_code/agents/deep/calibration_loop.py +144 -0
  36. aria_code/agents/deep/critic.py +125 -0
  37. aria_code/agents/deep/deepen.py +193 -0
  38. aria_code/agents/deep/models.py +149 -0
  39. aria_code/agents/deep/pipeline.py +164 -0
  40. aria_code/agents/deep/quant_fusion.py +192 -0
  41. aria_code/agents/deep/themes.py +95 -0
  42. aria_code/agents/deep/tiers.py +106 -0
  43. aria_code/agents/engineering/__init__.py +14 -0
  44. aria_code/agents/engineering/coder.py +222 -0
  45. aria_code/agents/engineering/tester.py +145 -0
  46. aria_code/agents/financial/__init__.py +21 -0
  47. aria_code/agents/financial/cashflow_burn.py +119 -0
  48. aria_code/agents/financial/catalyst.py +279 -0
  49. aria_code/agents/financial/corporate_finance.py +175 -0
  50. aria_code/agents/financial/debate.py +224 -0
  51. aria_code/agents/financial/earnings.py +315 -0
  52. aria_code/agents/financial/fundamental.py +178 -0
  53. aria_code/agents/financial/macro.py +111 -0
  54. aria_code/agents/financial/news.py +231 -0
  55. aria_code/agents/financial/risk.py +146 -0
  56. aria_code/agents/financial/sector.py +279 -0
  57. aria_code/agents/financial/strategist.py +121 -0
  58. aria_code/agents/financial/stripe_revenue.py +127 -0
  59. aria_code/agents/financial/synthesis.py +274 -0
  60. aria_code/agents/financial/technical.py +277 -0
  61. aria_code/agents/orchestrator.py +44 -0
  62. aria_code/agents/portfolio_agent.py +392 -0
  63. aria_code/agents/realty/__init__.py +62 -0
  64. aria_code/agents/realty/asset_diagnosis.py +152 -0
  65. aria_code/agents/realty/business_match.py +166 -0
  66. aria_code/agents/realty/cashflow_verify.py +209 -0
  67. aria_code/agents/realty/contract_rules.py +210 -0
  68. aria_code/agents/realty/energy_anomaly.py +189 -0
  69. aria_code/agents/realty/exit_settlement.py +208 -0
  70. aria_code/agents/realty/fulfillment_risk.py +206 -0
  71. aria_code/agents/realty/ops_optimize.py +160 -0
  72. aria_code/agents/realty/revenue_share.py +215 -0
  73. aria_code/agents/registry.py +168 -0
  74. aria_code/agents/signal_scheme.py +111 -0
  75. aria_code/agents/sports/__init__.py +0 -0
  76. aria_code/agents/sports/football_agent.py +197 -0
  77. aria_code/agents/supervisor.py +113 -0
  78. aria_code/agents/team.py +500 -0
  79. aria_code/agents/video/__init__.py +16 -0
  80. aria_code/agents/video/director.py +376 -0
  81. aria_code/agents/video/qa_agent.py +143 -0
  82. aria_code/agents/video/script_doctor.py +112 -0
  83. aria_code/agents/warehouse/__init__.py +25 -0
  84. aria_code/agents/warehouse/contracts.py +26 -0
  85. aria_code/agents/warehouse/dashboard.py +155 -0
  86. aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
  87. aria_code/agents/warehouse/inbound_exceptions.py +25 -0
  88. aria_code/agents/warehouse/inventory_health.py +24 -0
  89. aria_code/agents/warehouse/local_run.py +71 -0
  90. aria_code/agents/warehouse/local_snapshot.py +132 -0
  91. aria_code/agents/warehouse/logistics_cost.py +84 -0
  92. aria_code/agents/warehouse/logistics_sync.py +21 -0
  93. aria_code/agents/warehouse/navigation.schema.json +110 -0
  94. aria_code/agents/warehouse/task_agent.py +172 -0
  95. aria_code/agents/warehouse/workflow.py +31 -0
  96. aria_code/aliyun_data_client.py +4 -0
  97. aria_code/apps/README.md +12 -0
  98. aria_code/apps/__init__.py +2 -0
  99. aria_code/apps/channels/README.md +9 -0
  100. aria_code/apps/channels/__init__.py +17 -0
  101. aria_code/apps/channels/approvals.py +11 -0
  102. aria_code/apps/channels/conversation.py +11 -0
  103. aria_code/apps/channels/digest.py +11 -0
  104. aria_code/apps/channels/intake.py +92 -0
  105. aria_code/apps/channels/registry.py +11 -0
  106. aria_code/apps/channels/tradingview.py +94 -0
  107. aria_code/apps/cli/README.md +13 -0
  108. aria_code/apps/cli/__init__.py +2 -0
  109. aria_code/apps/cli/_optional.py +120 -0
  110. aria_code/apps/cli/app_server.py +656 -0
  111. aria_code/apps/cli/auto_update.py +125 -0
  112. aria_code/apps/cli/bootstrap.py +206 -0
  113. aria_code/apps/cli/broker_render.py +206 -0
  114. aria_code/apps/cli/chat_turn.py +1327 -0
  115. aria_code/apps/cli/cloud_models.py +231 -0
  116. aria_code/apps/cli/codegen_paths.py +29 -0
  117. aria_code/apps/cli/commands/__init__.py +34 -0
  118. aria_code/apps/cli/commands/_business_render.py +78 -0
  119. aria_code/apps/cli/commands/_ui.py +71 -0
  120. aria_code/apps/cli/commands/analysis_cmds.py +378 -0
  121. aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
  122. aria_code/apps/cli/commands/auth_cmds.py +215 -0
  123. aria_code/apps/cli/commands/backtest_cmds.py +2518 -0
  124. aria_code/apps/cli/commands/broker_cmds.py +1236 -0
  125. aria_code/apps/cli/commands/business_workflow_cmds.py +379 -0
  126. aria_code/apps/cli/commands/canvas_cmds.py +77 -0
  127. aria_code/apps/cli/commands/catalog.py +189 -0
  128. aria_code/apps/cli/commands/core_cmds.py +2748 -0
  129. aria_code/apps/cli/commands/data_cmds.py +521 -0
  130. aria_code/apps/cli/commands/diagnostic_cmds.py +285 -0
  131. aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
  132. aria_code/apps/cli/commands/engineering_cmds.py +399 -0
  133. aria_code/apps/cli/commands/file_cmds.py +312 -0
  134. aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
  135. aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
  136. aria_code/apps/cli/commands/market.py +544 -0
  137. aria_code/apps/cli/commands/market_cmds.py +1419 -0
  138. aria_code/apps/cli/commands/market_context.py +432 -0
  139. aria_code/apps/cli/commands/market_render.py +7 -0
  140. aria_code/apps/cli/commands/model_cmds.py +2374 -0
  141. aria_code/apps/cli/commands/ops_cmds.py +721 -0
  142. aria_code/apps/cli/commands/orchestrator_cmds.py +181 -0
  143. aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
  144. aria_code/apps/cli/commands/portfolio_cmds.py +1316 -0
  145. aria_code/apps/cli/commands/report.py +476 -0
  146. aria_code/apps/cli/commands/scaffold_templates.py +617 -0
  147. aria_code/apps/cli/commands/session_cmds.py +236 -0
  148. aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
  149. aria_code/apps/cli/commands/team.py +723 -0
  150. aria_code/apps/cli/commands/team_render.py +8 -0
  151. aria_code/apps/cli/commands/ui_cmds.py +401 -0
  152. aria_code/apps/cli/commands/warehouse_cmds.py +297 -0
  153. aria_code/apps/cli/commands/workflow_cmds.py +629 -0
  154. aria_code/apps/cli/commands/workspace_cmds.py +1543 -0
  155. aria_code/apps/cli/config_paths.py +73 -0
  156. aria_code/apps/cli/config_store.py +158 -0
  157. aria_code/apps/cli/constants.py +88 -0
  158. aria_code/apps/cli/context.py +34 -0
  159. aria_code/apps/cli/conversation_policy.py +53 -0
  160. aria_code/apps/cli/deterministic.py +200 -0
  161. aria_code/apps/cli/direct.py +48 -0
  162. aria_code/apps/cli/earnings_view.py +172 -0
  163. aria_code/apps/cli/endpoint_migration.py +64 -0
  164. aria_code/apps/cli/exec_events.py +255 -0
  165. aria_code/apps/cli/file_references.py +155 -0
  166. aria_code/apps/cli/finance_service_catalog.py +111 -0
  167. aria_code/apps/cli/football_reports.py +231 -0
  168. aria_code/apps/cli/github_app_auth.py +135 -0
  169. aria_code/apps/cli/google_login.py +239 -0
  170. aria_code/apps/cli/handlers/__init__.py +11 -0
  171. aria_code/apps/cli/handlers/broker_handlers.py +122 -0
  172. aria_code/apps/cli/handlers/chart_handlers.py +1314 -0
  173. aria_code/apps/cli/handlers/market_handlers.py +2570 -0
  174. aria_code/apps/cli/handlers/realty_handlers.py +114 -0
  175. aria_code/apps/cli/handlers/sports_handlers.py +22 -0
  176. aria_code/apps/cli/handlers/strategy_advice.py +82 -0
  177. aria_code/apps/cli/headless.py +352 -0
  178. aria_code/apps/cli/helpers.py +326 -0
  179. aria_code/apps/cli/hooks.py +180 -0
  180. aria_code/apps/cli/i18n.py +310 -0
  181. aria_code/apps/cli/intent.py +136 -0
  182. aria_code/apps/cli/intent_router.py +305 -0
  183. aria_code/apps/cli/intent_shadow.py +72 -0
  184. aria_code/apps/cli/intent_signals.py +347 -0
  185. aria_code/apps/cli/lifecycle_hooks.py +48 -0
  186. aria_code/apps/cli/local_tool_schemas.py +550 -0
  187. aria_code/apps/cli/main.py +39 -0
  188. aria_code/apps/cli/market_hours.py +100 -0
  189. aria_code/apps/cli/market_metadata.py +135 -0
  190. aria_code/apps/cli/market_universe.py +487 -0
  191. aria_code/apps/cli/message_processing.py +310 -0
  192. aria_code/apps/cli/mixin_binding.py +32 -0
  193. aria_code/apps/cli/model_catalog.py +652 -0
  194. aria_code/apps/cli/model_probe.py +169 -0
  195. aria_code/apps/cli/model_retirement.py +65 -0
  196. aria_code/apps/cli/native_branding.py +12 -0
  197. aria_code/apps/cli/native_bridge.py +118 -0
  198. aria_code/apps/cli/native_entry.py +58 -0
  199. aria_code/apps/cli/pdf_report.py +977 -0
  200. aria_code/apps/cli/plan_mode.py +139 -0
  201. aria_code/apps/cli/plotly_html.py +15 -0
  202. aria_code/apps/cli/prediction_feedback.py +202 -0
  203. aria_code/apps/cli/preflight.py +502 -0
  204. aria_code/apps/cli/project_aria.py +60 -0
  205. aria_code/apps/cli/prompt_assembly.py +78 -0
  206. aria_code/apps/cli/prompts/__init__.py +0 -0
  207. aria_code/apps/cli/prompts/coding.py +71 -0
  208. aria_code/apps/cli/prompts/select.py +193 -0
  209. aria_code/apps/cli/prompts/system_prompts.py +562 -0
  210. aria_code/apps/cli/prompts/ui.py +436 -0
  211. aria_code/apps/cli/provider_endpoints.py +237 -0
  212. aria_code/apps/cli/provider_keys.py +73 -0
  213. aria_code/apps/cli/providers/__init__.py +1 -0
  214. aria_code/apps/cli/providers/base.py +775 -0
  215. aria_code/apps/cli/providers/chat_routing.py +139 -0
  216. aria_code/apps/cli/providers/collaboration.py +98 -0
  217. aria_code/apps/cli/providers/llm/__init__.py +1 -0
  218. aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
  219. aria_code/apps/cli/providers/llm/ollama_stream.py +1574 -0
  220. aria_code/apps/cli/providers/llm/response_cache.py +60 -0
  221. aria_code/apps/cli/providers/llm/sse_stream.py +298 -0
  222. aria_code/apps/cli/providers/runtime_bridge.py +586 -0
  223. aria_code/apps/cli/providers/vertexai_stream.py +592 -0
  224. aria_code/apps/cli/release_download.py +159 -0
  225. aria_code/apps/cli/review_runner.py +111 -0
  226. aria_code/apps/cli/runtime_consumer.py +769 -0
  227. aria_code/apps/cli/sandbox.py +32 -0
  228. aria_code/apps/cli/session_export.py +120 -0
  229. aria_code/apps/cli/session_jsonl.py +208 -0
  230. aria_code/apps/cli/session_refresh.py +123 -0
  231. aria_code/apps/cli/session_store.py +125 -0
  232. aria_code/apps/cli/skills_catalog.py +273 -0
  233. aria_code/apps/cli/task_isolation.py +211 -0
  234. aria_code/apps/cli/terminal_chrome.py +412 -0
  235. aria_code/apps/cli/todo_tracker.py +190 -0
  236. aria_code/apps/cli/tool_executor.py +932 -0
  237. aria_code/apps/cli/tool_registry.py +34 -0
  238. aria_code/apps/cli/tool_scope.py +113 -0
  239. aria_code/apps/cli/tools/__init__.py +40 -0
  240. aria_code/apps/cli/tools/context.py +46 -0
  241. aria_code/apps/cli/tools/file_tools.py +181 -0
  242. aria_code/apps/cli/tools/market_tools.py +626 -0
  243. aria_code/apps/cli/tools/notebook_tools.py +111 -0
  244. aria_code/apps/cli/tools/system_tools.py +656 -0
  245. aria_code/apps/cli/tools/write_tools.py +961 -0
  246. aria_code/apps/cli/tradingview_bridge.py +529 -0
  247. aria_code/apps/cli/transactions.py +269 -0
  248. aria_code/apps/cli/turn_planning.py +137 -0
  249. aria_code/apps/cli/update_check.py +257 -0
  250. aria_code/apps/cli/updater.py +373 -0
  251. aria_code/apps/cli/utils/__init__.py +0 -0
  252. aria_code/apps/cli/utils/market_detect.py +1663 -0
  253. aria_code/apps/cli/web_login.py +136 -0
  254. aria_code/apps/cli/workflow_runner.py +127 -0
  255. aria_code/apps/cli/workspace_route.py +121 -0
  256. aria_code/apps/daemon/README.md +14 -0
  257. aria_code/apps/vscode/README.md +115 -0
  258. aria_code/apps/vscode/package.json +70 -0
  259. aria_code/aria_cli.py +5244 -0
  260. aria_code/aria_daemon.py +1372 -0
  261. aria_code/aria_feishu_bot.py +16 -0
  262. aria_code/aria_mcp_server.py +61 -0
  263. aria_code/aria_relay_client.py +4 -0
  264. aria_code/aria_telegram_bot.py +11 -0
  265. aria_code/ariarc.py +385 -0
  266. aria_code/artifacts.py +581 -0
  267. aria_code/backtest_engine.py +4 -0
  268. aria_code/backtest_report.py +4 -0
  269. aria_code/brokers/__init__.py +78 -0
  270. aria_code/brokers/automation.py +193 -0
  271. aria_code/brokers/base.py +231 -0
  272. aria_code/brokers/capabilities.py +264 -0
  273. aria_code/brokers/cn/__init__.py +10 -0
  274. aria_code/brokers/cn/easytrader_broker.py +193 -0
  275. aria_code/brokers/cn/futu_broker.py +194 -0
  276. aria_code/brokers/cn/longbridge_broker.py +190 -0
  277. aria_code/brokers/cn/tiger_broker.py +196 -0
  278. aria_code/brokers/cn/xtquant_broker.py +225 -0
  279. aria_code/brokers/config.py +414 -0
  280. aria_code/brokers/intl/__init__.py +5 -0
  281. aria_code/brokers/intl/alpaca_broker.py +183 -0
  282. aria_code/brokers/intl/ibkr_broker.py +215 -0
  283. aria_code/brokers/intl/trading212_broker.py +183 -0
  284. aria_code/brokers/intl/webull_broker.py +156 -0
  285. aria_code/brokers/paper_broker.py +266 -0
  286. aria_code/brokers/planning.py +296 -0
  287. aria_code/brokers/registry.py +213 -0
  288. aria_code/brokers/trading.py +267 -0
  289. aria_code/canva_client.py +4 -0
  290. aria_code/change_store.py +127 -0
  291. aria_code/clients/__init__.py +0 -0
  292. aria_code/clients/aliyun_data_client.py +660 -0
  293. aria_code/clients/aria_relay_client.py +16 -0
  294. aria_code/clients/canva_client.py +284 -0
  295. aria_code/clients/figma_client.py +100 -0
  296. aria_code/clients/football_data_client.py +1671 -0
  297. aria_code/clients/kling_video_client.py +182 -0
  298. aria_code/clients/market_data_client.py +1985 -0
  299. aria_code/clients/openai_image_client.py +242 -0
  300. aria_code/clients/runway_video_client.py +157 -0
  301. aria_code/clients/warehouse_erp_client.py +105 -0
  302. aria_code/cloud_memory.py +190 -0
  303. aria_code/command_safety.py +19 -0
  304. aria_code/computer_use_server.py +276 -0
  305. aria_code/computer_use_tools.py +4 -0
  306. aria_code/dashboard_generator.py +579 -0
  307. aria_code/data_analysis_tools.py +4 -0
  308. aria_code/data_cleaner.py +483 -0
  309. aria_code/data_service.py +513 -0
  310. aria_code/datasources/__init__.py +23 -0
  311. aria_code/datasources/base.py +166 -0
  312. aria_code/datasources/router.py +253 -0
  313. aria_code/datasources/sec_statements.py +217 -0
  314. aria_code/datasources/sources/__init__.py +15 -0
  315. aria_code/datasources/sources/akshare_source.py +269 -0
  316. aria_code/datasources/sources/alpha_vantage_source.py +203 -0
  317. aria_code/datasources/sources/edgar_source.py +219 -0
  318. aria_code/datasources/sources/finnhub_source.py +198 -0
  319. aria_code/datasources/sources/fred_source.py +223 -0
  320. aria_code/datasources/sources/tushare_source.py +143 -0
  321. aria_code/datasources/sources/web_scraper_source.py +278 -0
  322. aria_code/datasources/sources/world_bank_source.py +206 -0
  323. aria_code/datasources/sources/yfinance_source.py +152 -0
  324. aria_code/doctor.py +860 -0
  325. aria_code/domain/__init__.py +0 -0
  326. aria_code/domain/_finance_core/__init__.py +5 -0
  327. aria_code/domain/_finance_core/backtest_engine.py +325 -0
  328. aria_code/domain/_finance_core/performance_stats.py +101 -0
  329. aria_code/domain/backtest_engine.py +75 -0
  330. aria_code/domain/backtest_report.py +488 -0
  331. aria_code/domain/finance_formulas.py +389 -0
  332. aria_code/domain/performance_stats.py +6 -0
  333. aria_code/domain/portfolio_ledger.py +307 -0
  334. aria_code/domain/strategy_vault.py +421 -0
  335. aria_code/evals/__init__.py +37 -0
  336. aria_code/evals/behavior.py +113 -0
  337. aria_code/evals/gap.py +93 -0
  338. aria_code/evals/harness.py +949 -0
  339. aria_code/evals/review_probe.py +132 -0
  340. aria_code/evals/runner.py +425 -0
  341. aria_code/evals/scorecard.py +322 -0
  342. aria_code/evals/summary.py +125 -0
  343. aria_code/external_agent_runner.py +115 -0
  344. aria_code/figma_client.py +4 -0
  345. aria_code/file_analysis_tools.py +4 -0
  346. aria_code/finance_formulas.py +4 -0
  347. aria_code/football_data_client.py +4 -0
  348. aria_code/image_gen_tools.py +4 -0
  349. aria_code/intent_classifier.py +459 -0
  350. aria_code/kling_video_client.py +4 -0
  351. aria_code/licensing.py +151 -0
  352. aria_code/local_finance_tools.py +4 -0
  353. aria_code/local_image_provider.py +4 -0
  354. aria_code/local_llm_provider.py +4 -0
  355. aria_code/macro_tools.py +4 -0
  356. aria_code/markdown_pdf.py +289 -0
  357. aria_code/market_data_client.py +4 -0
  358. aria_code/mcp_client.py +744 -0
  359. aria_code/memory_manager.py +273 -0
  360. aria_code/model_capability.py +512 -0
  361. aria_code/notification_tools.py +4 -0
  362. aria_code/openai_image_client.py +4 -0
  363. aria_code/packages/__init__.py +26 -0
  364. aria_code/packages/adk_bridge/__init__.py +11 -0
  365. aria_code/packages/adk_bridge/code_review_tools.py +49 -0
  366. aria_code/packages/adk_bridge/market_tools.py +125 -0
  367. aria_code/packages/aria_agents/__init__.py +5 -0
  368. aria_code/packages/aria_agents/manifest.py +78 -0
  369. aria_code/packages/aria_core/__init__.py +34 -0
  370. aria_code/packages/aria_core/architecture.py +269 -0
  371. aria_code/packages/aria_core/export.py +147 -0
  372. aria_code/packages/aria_core/manifest.py +65 -0
  373. aria_code/packages/aria_core/paths.py +57 -0
  374. aria_code/packages/aria_core/secure_file.py +68 -0
  375. aria_code/packages/aria_infra/__init__.py +15 -0
  376. aria_code/packages/aria_infra/arthera.py +58 -0
  377. aria_code/packages/aria_infra/doctor.py +246 -0
  378. aria_code/packages/aria_infra/product.py +40 -0
  379. aria_code/packages/aria_mcp/__init__.py +25 -0
  380. aria_code/packages/aria_mcp/bridge.py +232 -0
  381. aria_code/packages/aria_mcp/circuit.py +82 -0
  382. aria_code/packages/aria_mcp/config.py +162 -0
  383. aria_code/packages/aria_mcp/installer.py +95 -0
  384. aria_code/packages/aria_mcp/server.py +1705 -0
  385. aria_code/packages/aria_mcp/tools.py +81 -0
  386. aria_code/packages/aria_sdk/__init__.py +19 -0
  387. aria_code/packages/aria_sdk/client.py +393 -0
  388. aria_code/packages/aria_sdk/providers.py +70 -0
  389. aria_code/packages/aria_sdk/streaming.py +75 -0
  390. aria_code/packages/aria_sdk/types.py +86 -0
  391. aria_code/packages/aria_services/__init__.py +92 -0
  392. aria_code/packages/aria_services/cache.py +60 -0
  393. aria_code/packages/aria_services/context.py +356 -0
  394. aria_code/packages/aria_services/context_checkpoints.py +157 -0
  395. aria_code/packages/aria_services/data.py +11 -0
  396. aria_code/packages/aria_services/provider_health.py +304 -0
  397. aria_code/packages/aria_services/references.py +247 -0
  398. aria_code/packages/aria_services/registry.py +249 -0
  399. aria_code/packages/aria_services/research_protocol.py +137 -0
  400. aria_code/packages/aria_services/research_quality.py +186 -0
  401. aria_code/packages/aria_services/settings.py +160 -0
  402. aria_code/packages/aria_services/usage.py +151 -0
  403. aria_code/packages/aria_skills/__init__.py +46 -0
  404. aria_code/packages/aria_skills/catalog.py +149 -0
  405. aria_code/packages/aria_skills/loader.py +455 -0
  406. aria_code/packages/aria_skills/registry.py +84 -0
  407. aria_code/packages/aria_tools/__init__.py +5 -0
  408. aria_code/packages/aria_tools/financial/__init__.py +5 -0
  409. aria_code/packages/aria_tools/financial/_paths.py +92 -0
  410. aria_code/packages/aria_tools/registry.py +129 -0
  411. aria_code/packages/quant_engine/__init__.py +33 -0
  412. aria_code/packages/quant_engine/_namespace.py +24 -0
  413. aria_code/packages/quant_engine/backtest/__init__.py +10 -0
  414. aria_code/packages/quant_engine/backtest/core.py +53 -0
  415. aria_code/packages/quant_engine/backtest/engine.py +102 -0
  416. aria_code/packages/quant_engine/portfolio/__init__.py +9 -0
  417. aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
  418. aria_code/packages/quant_engine/sports/__init__.py +76 -0
  419. aria_code/packages/quant_engine/sports/calibrator.py +354 -0
  420. aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
  421. aria_code/packages/quant_engine/sports/elo.py +300 -0
  422. aria_code/packages/quant_engine/sports/form.py +188 -0
  423. aria_code/packages/quant_engine/sports/h2h.py +195 -0
  424. aria_code/packages/quant_engine/sports/predictor.py +312 -0
  425. aria_code/packages/quant_engine/sports/tracker.py +665 -0
  426. aria_code/packages/quant_engine/stochastic/__init__.py +31 -0
  427. aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  428. aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
  429. aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  430. aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  431. aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
  432. aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  433. aria_code/packs/__init__.py +85 -0
  434. aria_code/packs/base.py +168 -0
  435. aria_code/packs/finance/__init__.py +250 -0
  436. aria_code/packs/healthcare/__init__.py +40 -0
  437. aria_code/packs/logistics/__init__.py +171 -0
  438. aria_code/packs/payments/__init__.py +139 -0
  439. aria_code/packs/realty/__init__.py +190 -0
  440. aria_code/packs/registry.py +202 -0
  441. aria_code/packs/rules.py +90 -0
  442. aria_code/plan_utils.py +194 -0
  443. aria_code/plugin_loader.py +328 -0
  444. aria_code/portfolio_ledger.py +4 -0
  445. aria_code/preview_server.py +449 -0
  446. aria_code/privacy/__init__.py +5 -0
  447. aria_code/privacy/feedback.py +123 -0
  448. aria_code/project_review.py +278 -0
  449. aria_code/project_review_api.py +235 -0
  450. aria_code/project_review_server.py +18 -0
  451. aria_code/project_tools.py +4 -0
  452. aria_code/providers/__init__.py +30 -0
  453. aria_code/providers/google_video_provider.py +256 -0
  454. aria_code/providers/llm/__init__.py +19 -0
  455. aria_code/providers/llm/anthropic.py +184 -0
  456. aria_code/providers/llm/arthera_gateway.py +90 -0
  457. aria_code/providers/llm/autoconfig.py +213 -0
  458. aria_code/providers/llm/base.py +139 -0
  459. aria_code/providers/llm/custom.py +158 -0
  460. aria_code/providers/llm/ollama.py +137 -0
  461. aria_code/providers/llm/openai_compat.py +350 -0
  462. aria_code/providers/llm/registry.py +457 -0
  463. aria_code/providers/local_image_provider.py +202 -0
  464. aria_code/providers/local_llm_provider.py +549 -0
  465. aria_code/providers/ollama_models.py +79 -0
  466. aria_code/providers/openai_params.py +32 -0
  467. aria_code/providers/tool_messages.py +100 -0
  468. aria_code/realty_data_tools.py +4 -0
  469. aria_code/report_exporters.py +150 -0
  470. aria_code/report_generator.py +1764 -0
  471. aria_code/review_service.py +497 -0
  472. aria_code/runtime/__init__.py +176 -0
  473. aria_code/runtime/acceptance.py +632 -0
  474. aria_code/runtime/agent_loop.py +1851 -0
  475. aria_code/runtime/agent_profiles.py +75 -0
  476. aria_code/runtime/approval.py +71 -0
  477. aria_code/runtime/baseline.py +219 -0
  478. aria_code/runtime/budget.py +232 -0
  479. aria_code/runtime/checkpoints.py +481 -0
  480. aria_code/runtime/consult.py +65 -0
  481. aria_code/runtime/contract.py +282 -0
  482. aria_code/runtime/delivery.py +432 -0
  483. aria_code/runtime/events.py +113 -0
  484. aria_code/runtime/gateway.py +167 -0
  485. aria_code/runtime/lsp.py +484 -0
  486. aria_code/runtime/native_index.py +153 -0
  487. aria_code/runtime/orchestrator.py +55 -0
  488. aria_code/runtime/processes.py +343 -0
  489. aria_code/runtime/project_graph.py +614 -0
  490. aria_code/runtime/repo_map.py +712 -0
  491. aria_code/runtime/research_bundle.py +105 -0
  492. aria_code/runtime/review.py +269 -0
  493. aria_code/runtime/run_state.py +127 -0
  494. aria_code/runtime/run_store.py +522 -0
  495. aria_code/runtime/scoped_runs.py +113 -0
  496. aria_code/runtime/self_healing.py +422 -0
  497. aria_code/runtime/semantic_diff.py +171 -0
  498. aria_code/runtime/subagent.py +721 -0
  499. aria_code/runtime/symbol_edit.py +117 -0
  500. aria_code/runtime/task_brief.py +46 -0
  501. aria_code/runtime/task_context.py +145 -0
  502. aria_code/runtime/task_ledger.py +108 -0
  503. aria_code/runtime/task_orchestrator.py +90 -0
  504. aria_code/runtime/task_outcome.py +33 -0
  505. aria_code/runtime/task_verification.py +266 -0
  506. aria_code/runtime/task_worktree.py +448 -0
  507. aria_code/runtime/tool_executor.py +329 -0
  508. aria_code/runtime/tool_policy.py +107 -0
  509. aria_code/runtime/transactions.py +216 -0
  510. aria_code/runtime/windows_job.py +115 -0
  511. aria_code/runtime/workflows.py +222 -0
  512. aria_code/runtime/worktrees.py +215 -0
  513. aria_code/runway_video_client.py +4 -0
  514. aria_code/safety/__init__.py +27 -0
  515. aria_code/safety/client_isolation.py +178 -0
  516. aria_code/safety/permissions.py +339 -0
  517. aria_code/safety/risk.py +699 -0
  518. aria_code/safety/sandbox.py +148 -0
  519. aria_code/safety/service.py +102 -0
  520. aria_code/service_endpoints.py +14 -0
  521. aria_code/setup_wizard.py +684 -0
  522. aria_code/spreadsheet_tools.py +4 -0
  523. aria_code/strategy_vault.py +4 -0
  524. aria_code/tools/__init__.py +0 -0
  525. aria_code/tools/artifact_tools.py +252 -0
  526. aria_code/tools/broker_tools.py +92 -0
  527. aria_code/tools/build_quant_engine.py +97 -0
  528. aria_code/tools/code_audit_tools.py +192 -0
  529. aria_code/tools/computer_use_tools.py +509 -0
  530. aria_code/tools/data_analysis_tools.py +831 -0
  531. aria_code/tools/enterprise_finance_tools.py +202 -0
  532. aria_code/tools/extended_tools.py +240 -0
  533. aria_code/tools/file_analysis_tools.py +880 -0
  534. aria_code/tools/image_gen_tools.py +114 -0
  535. aria_code/tools/local_finance_tools.py +3488 -0
  536. aria_code/tools/logistics_carriers.py +11 -0
  537. aria_code/tools/logistics_inventory.py +11 -0
  538. aria_code/tools/logistics_io.py +11 -0
  539. aria_code/tools/logistics_tenancy.py +11 -0
  540. aria_code/tools/logistics_tools.py +11 -0
  541. aria_code/tools/macro_tools.py +412 -0
  542. aria_code/tools/notification_tools.py +249 -0
  543. aria_code/tools/project_tools.py +525 -0
  544. aria_code/tools/realty_data_tools.py +662 -0
  545. aria_code/tools/spreadsheet_tools.py +399 -0
  546. aria_code/tools/stripe_tools.py +115 -0
  547. aria_code/tools/video_tools.py +500 -0
  548. aria_code/training/__init__.py +1 -0
  549. aria_code/training/compare.py +117 -0
  550. aria_code/training/prelude_commands.py +111 -0
  551. aria_code/training/prelude_eval.py +390 -0
  552. aria_code/training/prelude_real.py +183 -0
  553. aria_code/training/prelude_train.py +248 -0
  554. aria_code/training/sft_dataset.py +230 -0
  555. aria_code/training/trajectory_dataset.py +263 -0
  556. aria_code/ui/__init__.py +105 -0
  557. aria_code/ui/banner.py +404 -0
  558. aria_code/ui/completer.py +571 -0
  559. aria_code/ui/console.py +285 -0
  560. aria_code/ui/input_box.py +505 -0
  561. aria_code/ui/mentions.py +206 -0
  562. aria_code/ui/picker.py +248 -0
  563. aria_code/ui/render/__init__.py +11 -0
  564. aria_code/ui/render/actions.py +297 -0
  565. aria_code/ui/render/finance.py +1732 -0
  566. aria_code/ui/render/market.py +253 -0
  567. aria_code/ui/render/output.py +954 -0
  568. aria_code/ui/render/responsive.py +75 -0
  569. aria_code/ui/render/team.py +367 -0
  570. aria_code/ui/robot.py +239 -0
  571. aria_code/ui/startup_dashboard.py +110 -0
  572. aria_code/video_analysis.py +147 -0
  573. aria_code/video_editor.py +518 -0
  574. aria_code/video_workflow/__init__.py +8 -0
  575. aria_code/video_workflow/__main__.py +61 -0
  576. aria_code/video_workflow/engines/__init__.py +18 -0
  577. aria_code/video_workflow/engines/base.py +69 -0
  578. aria_code/video_workflow/engines/custom_model.py +204 -0
  579. aria_code/video_workflow/engines/google_veo.py +94 -0
  580. aria_code/video_workflow/engines/local_engine.py +71 -0
  581. aria_code/video_workflow/engines/registry.py +98 -0
  582. aria_code/video_workflow/google_models.py +278 -0
  583. aria_code/video_workflow/music/__init__.py +5 -0
  584. aria_code/video_workflow/music/foley_service.py +132 -0
  585. aria_code/video_workflow/music/service.py +212 -0
  586. aria_code/video_workflow/pipeline.py +620 -0
  587. aria_code/video_workflow/plan.py +202 -0
  588. aria_code/video_workflow/session.py +297 -0
  589. aria_code/workspace/__init__.py +6 -0
  590. aria_code/workspace/files.py +219 -0
  591. aria_code/workspace/verify.py +167 -0
  592. aria_code/workspace_changes.py +199 -0
  593. aria_code/workspace_service.py +196 -0
  594. aria_code/workspace_tools.py +150 -0
  595. aria_code-0.167.0.dist-info/METADATA +330 -0
  596. aria_code-0.167.0.dist-info/RECORD +601 -0
  597. aria_code-0.167.0.dist-info/WHEEL +5 -0
  598. aria_code-0.167.0.dist-info/entry_points.txt +5 -0
  599. aria_code-0.167.0.dist-info/licenses/LICENSE +201 -0
  600. aria_code-0.167.0.dist-info/licenses/NOTICE +49 -0
  601. aria_code-0.167.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,326 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ aria_bots.relay_client — 连接 Aria 中继服务器的 WebSocket 客户端
4
+ ================================================================
5
+ 本机运行,把中继服务器转发来的飞书消息交给 aria_feishu_bot 处理,
6
+ 并把 LLM 回复返回给中继服务器,再由服务器推送到飞书。
7
+
8
+ 启动方式:
9
+ python3 aria_relay_client.py # 单次连接(断线自动重连)
10
+ python3 aria_relay_client.py --once # 调试:收到第一条消息后退出
11
+
12
+ 所需环境变量(读取 ~/.aria/.env):
13
+ ARIA_RELAY_URL 中继地址,配置向导默认填 Arthera 运营的中继(见 OFFICIAL_RELAY_URL)
14
+ ARIA_RELAY_CLIENT_ID setup_wizard 生成的 12 位 hex id
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import asyncio
20
+ import json
21
+ import logging
22
+ import os
23
+ import sys
24
+ import time
25
+ from pathlib import Path
26
+
27
+ logger = logging.getLogger("aria.relay_client")
28
+
29
+ # ── 加载 ~/.aria/.env ──────────────────────────────────────────────────────
30
+
31
+ def _load_env() -> None:
32
+ env_file = Path.home() / ".aria" / ".env"
33
+ if env_file.exists():
34
+ for line in env_file.read_text().splitlines():
35
+ line = line.strip()
36
+ if line and not line.startswith("#") and "=" in line:
37
+ k, _, v = line.partition("=")
38
+ k = k.strip()
39
+ if k not in os.environ:
40
+ os.environ[k] = v.strip()
41
+
42
+
43
+ _load_env()
44
+
45
+ # ── This machine's relay credentials ──────────────────────────────────────────
46
+ #
47
+ # The relay records both on first registration (as hashes): the token proves a
48
+ # later connection is this machine, and the bind code is what a Feishu user
49
+ # sends to bind to it. Both stay in ~/.aria/.env. Before 2026-10-04 there were
50
+ # neither: the client_id was the only identity and also the bind code, so
51
+ # anyone who learned it could impersonate the machine or bind their own Feishu
52
+ # account to it.
53
+
54
+ _BIND_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" # nothing to misread: no 0/O, 1/I
55
+
56
+
57
+ def new_token() -> str:
58
+ import secrets
59
+ return secrets.token_urlsafe(32)
60
+
61
+
62
+ def new_bind_code() -> str:
63
+ import secrets
64
+ return "".join(secrets.choice(_BIND_ALPHABET) for _ in range(12))
65
+
66
+
67
+ def format_bind_code(code: str) -> str:
68
+ """ABCDEFGHJKLM → ARIA-BIND-ABCD-EFGH-JKLM, the form a person types."""
69
+ code = "".join(ch for ch in code.upper() if ch.isalnum())
70
+ return "ARIA-BIND-" + "-".join(code[i:i + 4] for i in range(0, len(code), 4))
71
+
72
+
73
+ def ensure_credentials(env_file: Path = Path.home() / ".aria" / ".env") -> tuple[str, str]:
74
+ """This machine's (token, bind code), created and saved on first use."""
75
+ token = os.environ.get("ARIA_RELAY_CLIENT_TOKEN", "").strip()
76
+ code = os.environ.get("ARIA_RELAY_BIND_CODE", "").strip()
77
+ fresh = {}
78
+ if len(token) < 32:
79
+ token = fresh["ARIA_RELAY_CLIENT_TOKEN"] = new_token()
80
+ if len(code) < 10:
81
+ code = fresh["ARIA_RELAY_BIND_CODE"] = new_bind_code()
82
+ if fresh:
83
+ env_file.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
84
+ with env_file.open("a", encoding="utf-8") as handle:
85
+ for key, value in fresh.items():
86
+ handle.write(f"{key}={value}\n")
87
+ env_file.chmod(0o600)
88
+ os.environ.update(fresh)
89
+ if "ARIA_RELAY_BIND_CODE" in fresh:
90
+ logger.info("New relay bind code — send this to the Aria bot in Feishu: /bind %s",
91
+ format_bind_code(code))
92
+ return token, code
93
+
94
+
95
+ # The relay Arthera runs. Offered as the setup wizard's default, never used as
96
+ # a silent fallback.
97
+ OFFICIAL_RELAY_URL = "wss://aria-code-741336310848.europe-west2.run.app/ws"
98
+
99
+ # The default used to be wss://relay.aria.ai. aria.ai belongs to an unrelated
100
+ # party (registered 2017); the subdomain does not resolve today, but its owner
101
+ # could create it at any time, and every client left on the default would then
102
+ # send its machine token, bind code and Feishu traffic there. Anyone who ran
103
+ # the wizard before 2026-10-04 has it saved in ~/.aria/.env, so it is refused
104
+ # by name, not merely no longer suggested.
105
+ _UNOWNED_RELAY_HOSTS = {"relay.aria.ai"}
106
+
107
+
108
+ def relay_url_problem(url: str) -> str:
109
+ """"" when the client may connect to `url`, otherwise why not."""
110
+ from urllib.parse import urlparse
111
+
112
+ if not url:
113
+ return "ARIA_RELAY_URL is not set — run the setup wizard (relay mode) to configure it"
114
+ parsed = urlparse(url)
115
+ host = (parsed.hostname or "").lower()
116
+ if host in _UNOWNED_RELAY_HOSTS or host.endswith(".aria.ai") or host == "aria.ai":
117
+ return (f"{host} is not run by Arthera; run the setup wizard again to switch to "
118
+ f"{OFFICIAL_RELAY_URL}")
119
+ if parsed.scheme == "wss" and host:
120
+ return ""
121
+ if parsed.scheme == "ws" and host in ("localhost", "127.0.0.1", "::1"):
122
+ return ""
123
+ return "ARIA_RELAY_URL must be wss:// — this machine's token is sent when it connects (ws:// only to localhost)"
124
+
125
+
126
+ def default_relay_url(current: str) -> str:
127
+ """What the setup wizard offers: the saved URL, unless it is unusable."""
128
+ current = (current or "").strip()
129
+ return current if current and not relay_url_problem(current) else OFFICIAL_RELAY_URL
130
+
131
+
132
+ _RELAY_URL = os.environ.get("ARIA_RELAY_URL", "").strip()
133
+ _CLIENT_ID = os.environ.get("ARIA_RELAY_CLIENT_ID", "")
134
+ _RECONNECT_DELAY_MAX = 60 # seconds
135
+ _RECONNECT_DELAY_BASE = 3
136
+
137
+
138
+ # ── 本地 aria_feishu_bot import ───────────────────────────────────────────────
139
+
140
+ def _get_feishu_bot():
141
+ try:
142
+ from aria_bots import feishu_bot
143
+ return feishu_bot
144
+ except ImportError as e:
145
+ logger.warning("feishu_bot not importable: %s", e)
146
+ return None
147
+
148
+
149
+ # ── Message handler ───────────────────────────────────────────────────────────
150
+
151
+ async def _handle_message(raw_msg: dict, ws) -> None:
152
+ """
153
+ Server sends:
154
+ {"type": "message", "id": "req_xxx", "payload": <feishu_event_dict>}
155
+
156
+ We reply:
157
+ {"type": "response", "id": "req_xxx", "result": <any>}
158
+ """
159
+ req_id = raw_msg.get("id", "")
160
+ payload = raw_msg.get("payload", {})
161
+
162
+ bot = _get_feishu_bot()
163
+ if bot is None:
164
+ result = {"error": "feishu_bot unavailable"}
165
+ else:
166
+ try:
167
+ # The relay routes an event only to the machine its sender bound
168
+ # with a code shown here, so that binding is the authorization.
169
+ result = await bot.dispatch_event(payload, authorized_by_binding=True)
170
+ except Exception as e:
171
+ logger.exception("dispatch_event error")
172
+ result = {"error": str(e)[:300]}
173
+
174
+ reply = json.dumps({"type": "response", "id": req_id, "result": result})
175
+ await ws.send(reply)
176
+
177
+
178
+ def register_frame(client_id: str, token: str, bind_code: str) -> dict:
179
+ frame = {"type": "register", "client_id": client_id, "token": token, "bind_code": bind_code}
180
+ secret = os.environ.get("ARIA_RELAY_SECRET", "").strip()
181
+ if secret:
182
+ frame["secret"] = secret # the deployment-wide gate, when the relay sets one
183
+ return frame
184
+
185
+
186
+ # ── Sending through the relay ─────────────────────────────────────────────────
187
+ #
188
+ # This machine has no Feishu app credentials in relay mode, so the bot hands
189
+ # each outgoing message to the relay, which sends it — and refuses anything
190
+ # that is not a reply to a message it forwarded here, or a post to a chat this
191
+ # user has spoken to the bot in.
192
+
193
+ _pending_sends: dict[str, asyncio.Future] = {}
194
+ _SEND_TIMEOUT = 20
195
+
196
+
197
+ def _relay_sender(ws):
198
+ async def send(request: dict) -> dict:
199
+ import uuid
200
+ send_id = f"send_{uuid.uuid4().hex[:10]}"
201
+ future = asyncio.get_running_loop().create_future()
202
+ _pending_sends[send_id] = future
203
+ try:
204
+ await ws.send(json.dumps({"type": "send", "id": send_id, **request}))
205
+ return await asyncio.wait_for(future, timeout=_SEND_TIMEOUT)
206
+ except asyncio.TimeoutError:
207
+ return {"code": -1, "msg": "relay did not confirm the send"}
208
+ finally:
209
+ _pending_sends.pop(send_id, None)
210
+ return send
211
+
212
+
213
+ def _settle_send(msg: dict) -> None:
214
+ future = _pending_sends.get(msg.get("id", ""))
215
+ if future is not None and not future.done():
216
+ future.set_result(msg.get("result") or {})
217
+
218
+
219
+ # ── Main loop ─────────────────────────────────────────────────────────────────
220
+
221
+ async def _connect_and_serve(once: bool = False) -> None:
222
+ try:
223
+ import websockets # type: ignore
224
+ except ImportError:
225
+ logger.error("websockets package not installed — run: pip install websockets")
226
+ sys.exit(1)
227
+
228
+ problem = relay_url_problem(_RELAY_URL)
229
+ if problem:
230
+ logger.error("Not connecting to the relay: %s", problem)
231
+ sys.exit(1)
232
+
233
+ if not _CLIENT_ID:
234
+ logger.error(
235
+ "ARIA_RELAY_CLIENT_ID is not set. "
236
+ "Run setup_wizard.py to generate your client ID."
237
+ )
238
+ sys.exit(1)
239
+
240
+ delay = _RECONNECT_DELAY_BASE
241
+ while True:
242
+ try:
243
+ logger.info("Connecting to %s (client_id=%s)", _RELAY_URL, _CLIENT_ID)
244
+ async with websockets.connect(
245
+ _RELAY_URL,
246
+ ping_interval=30,
247
+ ping_timeout=10,
248
+ open_timeout=15,
249
+ ) as ws:
250
+ # Register with server
251
+ token, bind_code = ensure_credentials()
252
+ await ws.send(json.dumps(register_frame(_CLIENT_ID, token, bind_code)))
253
+ ack_raw = await asyncio.wait_for(ws.recv(), timeout=10)
254
+ ack = json.loads(ack_raw)
255
+ if not ack.get("ok"):
256
+ logger.error("Registration rejected: %s", ack.get("reason", "unknown"))
257
+ if "another installation" in str(ack.get("reason", "")):
258
+ logger.error("This client_id belongs to a different machine on the relay. "
259
+ "Run the setup wizard to create a new one.")
260
+ await asyncio.sleep(delay)
261
+ continue
262
+
263
+ logger.info("Registered. Waiting for messages…")
264
+ delay = _RECONNECT_DELAY_BASE # reset on success
265
+ bot = _get_feishu_bot()
266
+ if bot is not None and hasattr(bot, "set_relay_sender"):
267
+ bot.set_relay_sender(_relay_sender(ws))
268
+
269
+ async for raw in ws:
270
+ try:
271
+ msg = json.loads(raw)
272
+ except json.JSONDecodeError:
273
+ logger.warning("Invalid JSON from relay: %r", raw[:100])
274
+ continue
275
+
276
+ if msg.get("type") == "ping":
277
+ await ws.send(json.dumps({"type": "pong"}))
278
+ continue
279
+
280
+ if msg.get("type") == "send_result":
281
+ _settle_send(msg)
282
+ continue
283
+
284
+ if msg.get("type") == "message":
285
+ asyncio.create_task(_handle_message(msg, ws))
286
+
287
+ if once:
288
+ return
289
+
290
+ except (OSError, ConnectionRefusedError) as e:
291
+ logger.warning("Connection failed: %s — retry in %ds", e, delay)
292
+ except asyncio.CancelledError:
293
+ logger.info("Relay client cancelled")
294
+ return
295
+ except Exception as e:
296
+ logger.warning("Relay error: %s — retry in %ds", e, delay)
297
+
298
+ # Disconnected: sends must not go to a closed socket while we retry.
299
+ bot = _get_feishu_bot()
300
+ if bot is not None and hasattr(bot, "set_relay_sender"):
301
+ bot.set_relay_sender(None)
302
+ await asyncio.sleep(delay)
303
+ delay = min(delay * 2, _RECONNECT_DELAY_MAX)
304
+
305
+
306
+ def main() -> None:
307
+ import argparse
308
+ parser = argparse.ArgumentParser(description="Aria 中继客户端")
309
+ parser.add_argument("--once", action="store_true", help="接收一条消息后退出(调试用)")
310
+ parser.add_argument("--verbose", "-v", action="store_true")
311
+ args = parser.parse_args()
312
+
313
+ logging.basicConfig(
314
+ level=logging.DEBUG if args.verbose else logging.INFO,
315
+ format="%(asctime)s [aria-relay] %(message)s",
316
+ datefmt="%H:%M:%S",
317
+ )
318
+
319
+ try:
320
+ asyncio.run(_connect_and_serve(once=args.once))
321
+ except KeyboardInterrupt:
322
+ pass
323
+
324
+
325
+ if __name__ == "__main__":
326
+ main()
@@ -0,0 +1,299 @@
1
+ """
2
+ aria_bots.telegram_bot — Telegram client and the aria-telegram-bot command handler.
3
+
4
+ Uses the Telegram Bot API directly via httpx (no heavy python-telegram-bot dep).
5
+ Supports long-polling updates and sending messages/documents back to users.
6
+
7
+ Commands handled:
8
+ /price SYMBOL — quick quote
9
+ /report SYMBOL — full analysis (async, returns text summary)
10
+ /brief — morning brief
11
+ /alerts — list active alerts
12
+ /alert SYMBOL cond v — add alert (e.g. /alert 600362 price_below 39.5)
13
+ /help — command list
14
+
15
+ Usage:
16
+ from aria_bots.telegram_bot import TelegramBot
17
+ bot = TelegramBot(token="...", allowed_ids={123456})
18
+ await bot.start(command_handler) # command_handler(cmd, args, chat_id) -> str
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import asyncio
23
+ import logging
24
+ from typing import Any, Callable, Coroutine, Optional, Set
25
+
26
+ import httpx
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ _API = "https://api.telegram.org/bot{token}/{method}"
31
+
32
+
33
+ class TelegramBot:
34
+ def __init__(
35
+ self,
36
+ token: str,
37
+ allowed_chat_ids: Optional[Set[int]] = None,
38
+ poll_timeout: int = 30,
39
+ ):
40
+ self.token = token
41
+ self.allowed_chat_ids = allowed_chat_ids or set()
42
+ self.poll_timeout = poll_timeout
43
+ self._offset = 0
44
+ self._running = False
45
+ self._client: Optional[httpx.AsyncClient] = None
46
+
47
+ def is_allowed(self, chat_id: int) -> bool:
48
+ """Fails closed: an empty allowlist allows nobody."""
49
+ return chat_id in self.allowed_chat_ids
50
+
51
+ # ── Low-level API ─────────────────────────────────────────────────────────
52
+
53
+ def _url(self, method: str) -> str:
54
+ return _API.format(token=self.token, method=method)
55
+
56
+ async def _call(self, method: str, **kwargs: Any) -> Optional[dict]:
57
+ try:
58
+ if self._client is None:
59
+ self._client = httpx.AsyncClient(timeout=self.poll_timeout + 5)
60
+ resp = await self._client.post(self._url(method), json=kwargs)
61
+ data = resp.json()
62
+ if not data.get("ok"):
63
+ logger.warning("Telegram %s error: %s", method, data.get("description"))
64
+ return None
65
+ return data.get("result")
66
+ except Exception as exc:
67
+ logger.error("Telegram API call %s failed: %s", method, exc)
68
+ return None
69
+
70
+ # ── Sending ───────────────────────────────────────────────────────────────
71
+
72
+ async def send_message(
73
+ self, chat_id: int, text: str, parse_mode: str = "Markdown"
74
+ ) -> bool:
75
+ # Telegram has a 4096-char message limit
76
+ if len(text) > 4096:
77
+ text = text[:4090] + "\n…"
78
+ result = await self._call(
79
+ "sendMessage",
80
+ chat_id=chat_id,
81
+ text=text,
82
+ parse_mode=parse_mode,
83
+ disable_web_page_preview=True,
84
+ )
85
+ return result is not None
86
+
87
+ async def send_long_message(self, chat_id: int, text: str) -> None:
88
+ """Split and send messages that exceed Telegram's 4096-char limit."""
89
+ chunks = [text[i : i + 4000] for i in range(0, len(text), 4000)]
90
+ for chunk in chunks:
91
+ await self.send_message(chat_id, chunk)
92
+
93
+ async def send_typing(self, chat_id: int) -> None:
94
+ await self._call("sendChatAction", chat_id=chat_id, action="typing")
95
+
96
+ async def send_document(
97
+ self, chat_id: int, file_path: str, caption: str = ""
98
+ ) -> bool:
99
+ """Send a file as a document attachment."""
100
+ try:
101
+ import aiofiles
102
+ url = self._url("sendDocument")
103
+ async with aiofiles.open(file_path, "rb") as f:
104
+ content = await f.read()
105
+ import os
106
+ filename = os.path.basename(file_path)
107
+ if self._client is None:
108
+ self._client = httpx.AsyncClient(timeout=60)
109
+ resp = await self._client.post(
110
+ url,
111
+ data={"chat_id": str(chat_id), "caption": caption},
112
+ files={"document": (filename, content)},
113
+ )
114
+ return resp.json().get("ok", False)
115
+ except ImportError:
116
+ # Fallback: just send caption as text
117
+ await self.send_message(chat_id, caption or "File ready (aiofiles not installed)")
118
+ return False
119
+ except Exception as exc:
120
+ logger.error("send_document failed: %s", exc)
121
+ return False
122
+
123
+ # ── Polling loop ─────────────────────────────────────────────────────────
124
+
125
+ async def get_updates(self) -> list[dict]:
126
+ result = await self._call(
127
+ "getUpdates",
128
+ offset=self._offset,
129
+ timeout=self.poll_timeout,
130
+ allowed_updates=["message"],
131
+ )
132
+ return result or []
133
+
134
+ async def start(
135
+ self,
136
+ command_handler: Callable[[str, str, int], Coroutine[Any, Any, str]],
137
+ ) -> None:
138
+ """
139
+ Start long-polling. For each received message, parse the command and
140
+ call command_handler(command, args, chat_id) → reply text.
141
+ command_handler should be an async coroutine.
142
+ """
143
+ self._running = True
144
+ logger.info("Telegram bot polling started")
145
+ while self._running:
146
+ try:
147
+ updates = await self.get_updates()
148
+ except asyncio.CancelledError:
149
+ break
150
+ except Exception as exc:
151
+ logger.error("Polling error: %s", exc)
152
+ await asyncio.sleep(5)
153
+ continue
154
+
155
+ for update in updates:
156
+ self._offset = max(self._offset, update["update_id"] + 1)
157
+ msg = update.get("message", {})
158
+ text = (msg.get("text") or "").strip()
159
+ chat_id = msg.get("chat", {}).get("id")
160
+ if not text or not chat_id:
161
+ continue
162
+
163
+ # ACL check. Fails closed: an empty TELEGRAM_ALLOWED_IDS allows
164
+ # nobody. It used to allow everybody — anyone who found the bot
165
+ # could drive the daemon's commands.
166
+ if not self.is_allowed(chat_id):
167
+ await self.send_message(
168
+ chat_id,
169
+ "⛔ 未授权。请将你的 Chat ID 添加到 `TELEGRAM_ALLOWED_IDS`。\n你的 ID: `" + str(chat_id) + "`",
170
+ )
171
+ continue
172
+
173
+ asyncio.create_task(self._handle(text, chat_id, command_handler))
174
+
175
+ async def _handle(
176
+ self,
177
+ text: str,
178
+ chat_id: int,
179
+ command_handler: Callable[[str, str, int], Coroutine[Any, Any, str]],
180
+ ) -> None:
181
+ # Parse "/command args" or plain text
182
+ if text.startswith("/"):
183
+ parts = text[1:].split(None, 1)
184
+ cmd = parts[0].lower().split("@")[0] # strip @botname suffix
185
+ args = parts[1] if len(parts) > 1 else ""
186
+ else:
187
+ cmd = "chat"
188
+ args = text
189
+
190
+ await self.send_typing(chat_id)
191
+ try:
192
+ reply = await command_handler(cmd, args, chat_id)
193
+ except Exception as exc:
194
+ reply = f"⚠️ 执行出错: {exc}"
195
+ logger.exception("command_handler error cmd=%s", cmd)
196
+
197
+ if reply:
198
+ await self.send_long_message(chat_id, reply)
199
+
200
+ async def stop(self) -> None:
201
+ self._running = False
202
+ if self._client:
203
+ await self._client.aclose()
204
+ self._client = None
205
+
206
+ async def get_me(self) -> Optional[dict]:
207
+ return await self._call("getMe")
208
+
209
+
210
+ # ── Commands (aria-telegram-bot) ─────────────────────────────────────────────
211
+
212
+ HELP = (
213
+ "*Aria 命令列表*\n\n"
214
+ "`/price SYMBOL` — 实时报价\n"
215
+ "`/report SYMBOL` — 研报\n"
216
+ "`/brief` — 今日晨报\n"
217
+ "`/alert SYMBOL cond value` — 添加价格预警\n"
218
+ " 条件: price\\_above / price\\_below / pct\\_change\\_above / pct\\_change\\_below\n"
219
+ "`/alerts` — 查看预警列表\n"
220
+ "`/status` — Daemon 运行状态\n"
221
+ "\n直接发文字也可对话 Aria。"
222
+ )
223
+
224
+
225
+ async def handle_command(cmd: str, args: str, chat_id: int) -> str:
226
+ """Answer one Telegram command: market commands go to aria-runtime daemon,
227
+ everything else is a turn on the Go engine."""
228
+ from . import engine, market
229
+
230
+ args = args.strip()
231
+ try:
232
+ if cmd in ("help", "start"):
233
+ return HELP
234
+ if cmd in ("price", "p"):
235
+ symbol = args.split()[0].upper() if args else ""
236
+ if not symbol:
237
+ return "用法: `/price AAPL` 或 `/price 600519.SS`"
238
+ title, body, _ = market.price_card(await market.quote(symbol))
239
+ return f"*{title}*\n" + body.replace("**", "*")
240
+ if cmd in ("brief", "morning", "briefing"):
241
+ title, body = await market.brief("morning-brief")
242
+ return f"*{title}*\n{body}"
243
+ if cmd == "alert":
244
+ parts = args.split()
245
+ if len(parts) != 3:
246
+ return "用法: `/alert AAPL price_above 200`"
247
+ symbol, condition, value = parts[0].upper(), parts[1], parts[2]
248
+ await market.add_alert(symbol, condition, value)
249
+ return f"✅ 预警已添加:*{symbol}* {condition} `{value}`"
250
+ if cmd == "alerts":
251
+ table = await market.list_alerts()
252
+ return "暂无预警" if len(table.splitlines()) < 2 else f"```\n{table}\n```"
253
+ if cmd == "status":
254
+ return await market.status()
255
+ except market.MarketError as exc:
256
+ return f"⚠️ {exc}"
257
+
258
+ runtime = engine.find_engine()
259
+ if not runtime:
260
+ return "⚠️ 未找到 aria-runtime:请安装,或用 ARIA_RUNTIME 指向它。"
261
+ if cmd == "report":
262
+ symbol = args.split()[0].upper() if args else ""
263
+ if not symbol:
264
+ return "用法: `/report AAPL` 或 `/report 600519.SS`"
265
+ prompt = (f"为 {symbol} 写一份简明研报:最新行情、技术面、基本面要点、主要风险和结论。"
266
+ "只用工具取到的数据,注明来源与日期。")
267
+ return await engine.run_turn(prompt, engine=runtime, timeout=240)
268
+ if cmd == "chat":
269
+ return await engine.run_turn(args, engine=runtime) if args else HELP
270
+ return f"未知命令 `/{cmd}`。发送 `/help` 查看命令列表。"
271
+
272
+
273
+ def allowed_ids(raw: str) -> Set[int]:
274
+ return {int(x.strip()) for x in raw.split(",") if x.strip().lstrip("-").isdigit()}
275
+
276
+
277
+ def main() -> int:
278
+ """aria-telegram-bot: long-poll Telegram with TELEGRAM_BOT_TOKEN, answering
279
+ only chats in TELEGRAM_ALLOWED_IDS."""
280
+ import os
281
+
282
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [telegram] %(levelname)s %(message)s")
283
+ token = os.environ.get("TELEGRAM_BOT_TOKEN", "").strip()
284
+ if not token:
285
+ logger.error("TELEGRAM_BOT_TOKEN is not set")
286
+ return 2
287
+ ids = allowed_ids(os.environ.get("TELEGRAM_ALLOWED_IDS", ""))
288
+ if not ids:
289
+ logger.warning("TELEGRAM_ALLOWED_IDS is empty: every chat will be refused")
290
+ bot = TelegramBot(token=token, allowed_chat_ids=ids)
291
+ try:
292
+ asyncio.run(bot.start(handle_command))
293
+ except KeyboardInterrupt:
294
+ pass
295
+ return 0
296
+
297
+
298
+ if __name__ == "__main__":
299
+ raise SystemExit(main())