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,823 @@
1
+
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import asyncio
6
+ import pathlib
7
+ import subprocess
8
+ import os
9
+ import re as re_module
10
+ import time
11
+ import sys
12
+ import re
13
+ from typing import Dict, Any, Optional
14
+ from datetime import datetime
15
+
16
+ from aria_code.runtime import ApprovalDecision, apply_approval_decision
17
+ from aria_code.runtime.tool_policy import check_tool_policy
18
+
19
+ try:
20
+ from rich import box as rich_box
21
+ from rich.panel import Panel
22
+ except ImportError: # The legacy CLI injects these when Rich is available.
23
+ rich_box = None
24
+ Panel = None
25
+
26
+ # This module is imported by ``aria_cli`` and its public functions are rebound
27
+ # to that module's globals for backward compatibility. Keep safe defaults so
28
+ # the extracted module can still be imported, linted and type-checked on its
29
+ # own. The defaults are deliberately conservative; interactive execution uses
30
+ # the values owned by ``aria_cli`` after rebinding.
31
+ _ACTIVE_NETWORK_ENABLED = [True]
32
+ _ARIA_BOT_MODE = False
33
+ _CONFIRM_TOOLS: set[str] = set()
34
+ _HAS_JSON_HOOKS = False
35
+ _JSON_HOOKS: dict[str, object] = {}
36
+ # Real values, not placeholders. These two used to be empty stubs here and were
37
+ # defined for real in aria_cli.py, so execute_aria_tool only cached when it ran
38
+ # as the rebound copy with aria_cli's globals — and a direct import of it
39
+ # silently got a TTL table with nothing in it, i.e. no caching at all. aria_cli
40
+ # imports them from here now, so both copies share one cache and one config.
41
+ _CACHE_TTL: dict[str, int] = {
42
+ "get_market_data": 30, "get_market_history": 300, "get_crypto_data": 30, "get_forex_data": 30,
43
+ "get_commodities_data": 60, "get_bonds_data": 60, "get_futures_data": 60,
44
+ "get_news": 300, "get_sector_performance": 60, "get_market_overview": 60,
45
+ }
46
+ _TOOL_CACHE: dict[str, tuple] = {} # key -> (result, timestamp)
47
+ _auto_approve_session = False
48
+ _session_always_allow: set[str] = set()
49
+ _session_command_prefixes: set[tuple[str, ...]] = set()
50
+
51
+
52
+ def _command_matches_session_prefix(command: str) -> bool:
53
+ return False
54
+
55
+
56
+ def _command_approval_prefix(command: str) -> tuple[str, ...]:
57
+ return ()
58
+
59
+
60
+ def _arrow_select(options, selected: int = 0, title: str = "") -> int:
61
+ return len(options) - 1
62
+
63
+
64
+ def _fire_json_hook(*args, **kwargs) -> bool:
65
+ return True
66
+
67
+ from aria_code.apps.cli.tools.system_tools import (
68
+ tool_run_command as _src_run_command,
69
+ tool_web_fetch as _src_web_fetch,
70
+ tool_github as _src_github,
71
+ )
72
+ from aria_code.apps.cli.tools.notebook_tools import (
73
+ tool_glob as _src_glob,
74
+ tool_notebook_read as _src_notebook_read,
75
+ tool_notebook_edit as _src_notebook_edit,
76
+ )
77
+ from aria_code.apps.cli.tools.file_tools import (
78
+ tool_read_file as _src_read_file,
79
+ tool_list_files as _src_list_files,
80
+ tool_search_code as _src_search_code,
81
+ )
82
+ from aria_code.apps.cli.tools.market_tools import (
83
+ tool_get_market_data as _src_get_market_data,
84
+ tool_get_market_history as _src_get_market_history,
85
+ tool_broker_query as _src_broker_query,
86
+ tool_broker_order as _src_broker_order,
87
+ )
88
+
89
+
90
+
91
+
92
+ def _g(name):
93
+ from aria_code import aria_cli
94
+ return getattr(aria_cli, name)
95
+
96
+ def _tool_analyze_file(params: dict) -> dict:
97
+ """Parse & analyse a local document/image (pdf/docx/xlsx/csv/image/…)."""
98
+ from aria_code.file_analysis_tools import tool_analyze_file as _f
99
+ return _f(params)
100
+ def _tool_read_file(params: dict) -> dict:
101
+ """Thin shim — implementation in apps/cli/tools/file_tools.py."""
102
+ return _src_read_file(params)
103
+ def _strip_markdown_fences(content: str) -> str:
104
+ """Thin shim — implementation in apps/cli/tools/write_tools.py."""
105
+ from aria_code.apps.cli.tools.write_tools import _strip_markdown_fences as _f
106
+ return _f(content)
107
+ def _auto_fix_python(content: str, path: str) -> str:
108
+ """Thin shim — implementation in apps/cli/tools/write_tools.py."""
109
+ from aria_code.apps.cli.tools.write_tools import _auto_fix_python as _f
110
+ return _f(content, path)
111
+ def _write_policy_confirm(p: pathlib.Path, content: str, existed: bool) -> tuple:
112
+ """Thin shim — implementation in apps/cli/tools/write_tools.py."""
113
+ from aria_code.apps.cli.tools.write_tools import _write_policy_confirm as _f
114
+ return _f(p, content, existed)
115
+ def _tool_write_file(params: dict) -> dict:
116
+ """Thin shim — implementation in apps/cli/tools/write_tools.py."""
117
+ from aria_code.apps.cli.tools.write_tools import tool_write_file as _f
118
+ return _f(params)
119
+ def _tool_edit_file(params: dict) -> dict:
120
+ """Thin shim — implementation in apps/cli/tools/write_tools.py."""
121
+ from aria_code.apps.cli.tools.write_tools import tool_edit_file as _f
122
+ return _f(params)
123
+ def _tool_multi_edit(params: dict) -> dict:
124
+ """Thin shim — implementation in apps/cli/tools/write_tools.py."""
125
+ from aria_code.apps.cli.tools.write_tools import tool_multi_edit as _f
126
+ return _f(params)
127
+ def _tool_update_todos(params: dict) -> dict:
128
+ """Thin shim — implementation in apps/cli/todo_tracker.py."""
129
+ from aria_code.apps.cli.todo_tracker import update_todos as _f
130
+ return _f(params)
131
+ def _tool_list_files(params: dict) -> dict:
132
+ """Thin shim — implementation in apps/cli/tools/file_tools.py."""
133
+ return _src_list_files(params)
134
+ def _tool_search_code(params: dict) -> dict:
135
+ """Thin shim — implementation in apps/cli/tools/file_tools.py."""
136
+ return _src_search_code(params)
137
+ def _tool_run_command(params: dict) -> dict:
138
+ """Run a shell command — thin wrapper supplying global defaults."""
139
+ params.setdefault("permission_mode", _g("_ACTIVE_PERMISSION_MODE")[0])
140
+ params.setdefault("network_enabled", _g("_ACTIVE_NETWORK_ENABLED")[0])
141
+ return _src_run_command(params, console=_g("console"), has_rich=_g("HAS_RICH"))
142
+ def _tool_web_fetch(params: dict) -> dict:
143
+ return _src_web_fetch(params)
144
+ def _tool_github(params: dict) -> dict:
145
+ params.setdefault("permission_mode", _g("_ACTIVE_PERMISSION_MODE")[0])
146
+ params.setdefault("network_enabled", _g("_ACTIVE_NETWORK_ENABLED")[0])
147
+ return _src_github(params, console=_g("console"), has_rich=_g("HAS_RICH"))
148
+ def _tool_glob(params: dict) -> dict:
149
+ return _src_glob(params)
150
+ def _tool_notebook_read(params: dict) -> dict:
151
+ return _src_notebook_read(params)
152
+ def _tool_notebook_edit(params: dict) -> dict:
153
+ return _src_notebook_edit(params)
154
+ def _tool_broker_query(params: dict) -> dict:
155
+ return _src_broker_query(params)
156
+ def _tool_broker_order(params: dict) -> dict:
157
+ return _src_broker_order(params)
158
+ def _tool_get_market_data(params: dict) -> dict:
159
+ return _src_get_market_data(params)
160
+ def _tool_get_market_history(params: dict) -> dict:
161
+ return _src_get_market_history(params)
162
+ def _todo_schema():
163
+ from aria_code.apps.cli.todo_tracker import UPDATE_TODOS_SCHEMA
164
+ return UPDATE_TODOS_SCHEMA
165
+ def _wrap_bare_schemas(bare: list) -> list:
166
+ """Convert Anthropic-style {name, description, parameters} schemas into the
167
+ OpenAI {type:function, function:{…}} envelope used by _g("LOCAL_TOOL_SCHEMAS").
168
+
169
+ The subagent and LSP modules declare schemas in the bare form; without this
170
+ wrapping their tools execute but are invisible to the model (it is never
171
+ told they exist).
172
+ """
173
+ wrapped = []
174
+ for s in bare or []:
175
+ if "function" in s: # already enveloped
176
+ wrapped.append(s)
177
+ else:
178
+ wrapped.append({"type": "function", "function": s})
179
+ return wrapped
180
+ def _dedup_tool_schemas() -> None:
181
+ """Drop duplicate tool schemas, keeping the LAST occurrence by name.
182
+
183
+ Some tools (e.g. web_fetch, get_market_data) are declared both by the
184
+ finance-tools registry and the static schema block. The static block runs
185
+ after and carries the richer description, so keeping the last copy wins.
186
+
187
+ Schemas arrive in two shapes: the OpenAI-style ``{"type": "function",
188
+ "function": {...}}`` wrapper and the bare ``{"name", "description",
189
+ "parameters"}`` form that several register_* helpers emit. Reading only
190
+ the wrapped shape gave a bare schema an empty name, and the ``if name``
191
+ guard then dropped it — silently, and only from the schema list, so the
192
+ handler stayed in LOCAL_TOOLS while the model was never told the tool
193
+ existed. analyze_logistics_data, analyze_stripe_data and
194
+ analyze_financial_statements were all unreachable this way: registered,
195
+ callable, and invisible.
196
+
197
+ It also normalises, which is why it reads both shapes and writes one.
198
+ Leaving the bare form in the list sends Ollama and the OpenAI-compatible
199
+ providers a schema they reject, and forces every consumer to handle two
200
+ shapes — one test already assumed the wrapper and broke on the other.
201
+ """
202
+ kept: dict = {}
203
+ for schema in _g("LOCAL_TOOL_SCHEMAS"):
204
+ name = (schema.get("function") or schema).get("name", "")
205
+ if not name:
206
+ continue # malformed: nothing can call it, nothing can describe it
207
+ kept[name] = schema if "function" in schema else {
208
+ "type": "function", "function": schema,
209
+ }
210
+ _g("LOCAL_TOOL_SCHEMAS")[:] = list(kept.values())
211
+ def _show_edit_preview(params: dict):
212
+ """Show a diff preview for edit_file (Claude Code style, Panel-boxed)."""
213
+ if _g("_ARIA_BOT_MODE"):
214
+ return
215
+ path = params.get("path", "")
216
+ old_str = params.get("old_string", params.get("old_str", ""))
217
+ new_str = params.get("new_string", params.get("new_str", ""))
218
+ if not path or not old_str:
219
+ return
220
+
221
+ p = pathlib.Path(path).expanduser().resolve()
222
+ short = p.name or "file tool"
223
+
224
+ if not _g("HAS_RICH"):
225
+ print(f"\n Edit file {short}")
226
+ return
227
+
228
+ body_parts: list = []
229
+ try:
230
+ content = p.read_text(errors="replace")
231
+ pos = content.find(old_str)
232
+ if pos >= 0:
233
+ line_num = content[:pos].count("\n") + 1
234
+ all_lines = content.splitlines()
235
+ old_lines = old_str.splitlines()
236
+ new_lines = new_str.splitlines()
237
+
238
+ # Context before (up to 2 lines)
239
+ ctx_start = max(0, line_num - 3)
240
+ for i in range(ctx_start, line_num - 1):
241
+ if i < len(all_lines):
242
+ body_parts.append(f"[dim]{i+1:4} {all_lines[i][:100]}[/dim]")
243
+
244
+ # Removed lines
245
+ for i, ol in enumerate(old_lines):
246
+ ln = line_num + i
247
+ body_parts.append(f"[red]{ln:4} - {ol[:100]}[/red]")
248
+
249
+ # Added lines
250
+ for i, nl in enumerate(new_lines):
251
+ ln = line_num + i
252
+ body_parts.append(f"[green]{ln:4} + {nl[:100]}[/green]")
253
+
254
+ # Context after (up to 2 lines)
255
+ after_start = line_num - 1 + len(old_lines)
256
+ for i in range(after_start, min(after_start + 2, len(all_lines))):
257
+ body_parts.append(f"[dim]{i+1:4} {all_lines[i][:100]}[/dim]")
258
+ else:
259
+ # String not found — fallback to plain diff lines
260
+ for ol in old_str.splitlines()[:6]:
261
+ body_parts.append(f"[red]- {ol[:100]}[/red]")
262
+ for nl in new_str.splitlines()[:6]:
263
+ body_parts.append(f"[green]+ {nl[:100]}[/green]")
264
+ except Exception:
265
+ for ol in old_str.splitlines()[:6]:
266
+ body_parts.append(f"[red]- {ol[:100]}[/red]")
267
+ for nl in new_str.splitlines()[:6]:
268
+ body_parts.append(f"[green]+ {nl[:100]}[/green]")
269
+
270
+ _g("console").print()
271
+ _g("console").print(Panel(
272
+ "\n".join(body_parts) if body_parts else "[dim](no preview)[/dim]",
273
+ title=f"[yellow]Edit file[/yellow] [dim]{short}[/dim]",
274
+ title_align="left",
275
+ border_style="yellow",
276
+ box=rich_box.ROUNDED,
277
+ padding=(0, 1),
278
+ ))
279
+ def _show_multi_edit_preview(params: dict):
280
+ """Show a compact preview of all edits in a multi_edit call."""
281
+ if _g("_ARIA_BOT_MODE"):
282
+ return
283
+ path = params.get("path", "")
284
+ edits = params.get("edits", [])
285
+ if not path or not edits:
286
+ return
287
+ if not _g("HAS_RICH"):
288
+ print(f"\n Multi-edit {path} ({len(edits)} edits)")
289
+ return
290
+ body_parts: list = []
291
+ for i, ed in enumerate(edits):
292
+ old_s = ed.get("old_string", ed.get("old_str", "")) if isinstance(ed, dict) else ""
293
+ new_s = ed.get("new_string", ed.get("new_str", "")) if isinstance(ed, dict) else ""
294
+ body_parts.append(f"[bold]#{i+1}[/bold]")
295
+ for ol in old_s.splitlines()[:3]:
296
+ body_parts.append(f"[red] - {ol[:96]}[/red]")
297
+ for nl in new_s.splitlines()[:3]:
298
+ body_parts.append(f"[green] + {nl[:96]}[/green]")
299
+ _g("console").print()
300
+ _g("console").print(Panel(
301
+ "\n".join(body_parts) if body_parts else "[dim](no preview)[/dim]",
302
+ title=f"[yellow]Multi-edit[/yellow] [dim]{pathlib.Path(path).name} · {len(edits)} edits[/dim]",
303
+ title_align="left",
304
+ border_style="yellow",
305
+ box=rich_box.ROUNDED,
306
+ padding=(0, 1),
307
+ ))
308
+ def _show_write_preview(params: dict):
309
+ """Show a content preview for write_file (Claude Code style, Panel-boxed)."""
310
+ if _g("_ARIA_BOT_MODE"):
311
+ return
312
+ path = params.get("path", "")
313
+ content = params.get("content", "")
314
+ if not path:
315
+ return
316
+ # Show cleaned content (without markdown fences)
317
+ content = _strip_markdown_fences(content)
318
+
319
+ p = pathlib.Path(path).expanduser().resolve()
320
+ short = p.name or "file tool"
321
+
322
+ existed = p.exists()
323
+ action = "Overwrite file" if existed else "Write new file"
324
+ action_color = "yellow" if existed else "green"
325
+ lines = content.count("\n") + 1
326
+
327
+ if not _g("HAS_RICH"):
328
+ print(f"\n {action} {short} ({lines} lines)")
329
+ return
330
+
331
+ if lines > 80:
332
+ body_parts = [
333
+ f"[dim]Large generated file: {lines} lines[/dim]",
334
+ "[dim]Preview suppressed; the full content will be written as an artifact/file.[/dim]",
335
+ "[dim]Use /read after creation if you need to inspect it.[/dim]",
336
+ ]
337
+ else:
338
+ preview_lines = content.splitlines()[:6]
339
+ body_parts = [f"[green]+ {pl[:100]}[/green]" for pl in preview_lines]
340
+ if lines > 6:
341
+ n = lines - 6
342
+ body_parts.append(f"[dim]… +{n} more line{'s' if n != 1 else ''}[/dim]")
343
+ body = "\n".join(body_parts)
344
+
345
+ _g("console").print()
346
+ _g("console").print(Panel(
347
+ body,
348
+ title=f"[{action_color}]{action}[/{action_color}] [dim]{short} ({lines} lines)[/dim]",
349
+ title_align="left",
350
+ border_style=action_color if existed else "dim",
351
+ box=rich_box.ROUNDED,
352
+ padding=(0, 1),
353
+ ))
354
+ def _apply_tool_approval(params: dict, decision: ApprovalDecision) -> dict:
355
+ """Apply approval state to CLI globals and execution params."""
356
+ # The two sets are only mutated in place, so only the rebound flag needs this.
357
+ global _auto_approve_session
358
+ if decision.auto_approve_session:
359
+ _auto_approve_session = True
360
+ if decision.tool_scope:
361
+ _session_always_allow.add(decision.tool_scope)
362
+ if decision.command_prefix:
363
+ _session_command_prefixes.add(tuple(decision.command_prefix))
364
+ return apply_approval_decision(params, decision)
365
+ def _confirm_tool_execution_decision(tool_name: str, params: dict,
366
+ config_policy: str = None) -> ApprovalDecision:
367
+ """Ask user to confirm before executing a destructive tool.
368
+ Returns a structured approval decision.
369
+
370
+ For run_command: pre-flight policy check happens HERE, before showing the
371
+ picker. If the command would be blocked even with user approval (high-risk),
372
+ show error immediately. If medium-risk with 'safe' policy, offer to upgrade
373
+ policy inline so the user can act without leaving the flow.
374
+ """
375
+ if config_policy is None:
376
+ config_policy = _g("_ACTIVE_COMMAND_POLICY")[0]
377
+ # ── Persistent per-tool policy check (allowlist / denylist) ─────────────
378
+ _policy_verdict = check_tool_policy(tool_name)
379
+ if _policy_verdict == "deny":
380
+ if _g("HAS_RICH"):
381
+ _g("console").print(
382
+ f" [red]✗ 工具 '{tool_name}' 被永久黑名单拒绝[/red] "
383
+ f"[dim](/config policy remove {tool_name} 可解除)[/dim]"
384
+ )
385
+ else:
386
+ print(f" ✗ '{tool_name}' blocked by tool policy")
387
+ return ApprovalDecision.deny("blocked by tool policy (deny list)")
388
+ if _policy_verdict == "allow":
389
+ if tool_name == "run_command":
390
+ return ApprovalDecision.allow(policy=config_policy, user_approved=True)
391
+ return ApprovalDecision.allow()
392
+
393
+ if _auto_approve_session:
394
+ # Still inject policy so run_command doesn't re-block
395
+ if tool_name == "run_command":
396
+ return ApprovalDecision.allow(policy=config_policy, user_approved=True)
397
+ return ApprovalDecision.allow()
398
+ if tool_name == "run_command" and _command_matches_session_prefix(params.get("command", "")):
399
+ return ApprovalDecision.allow(policy="balanced", user_approved=True)
400
+ # Per-tool session allow — user previously chose "Always allow [tool] this session"
401
+ if tool_name in _session_always_allow:
402
+ if tool_name == "run_command":
403
+ return ApprovalDecision.allow(policy=config_policy, user_approved=True)
404
+ return ApprovalDecision.allow()
405
+
406
+ # ── Plan mode: confirm ALL tools (even non-CONFIRM_TOOLS) ────────────────
407
+ if _g("_PLAN_MODE").active and tool_name not in _CONFIRM_TOOLS:
408
+ return _g("_PLAN_MODE").confirm_step(
409
+ tool_name, params,
410
+ console=_g("console"),
411
+ has_rich=_g("HAS_RICH"),
412
+ arrow_select_fn=_arrow_select,
413
+ )
414
+ # Ask-always policy: force confirmation even for normally silent tools
415
+ if _policy_verdict == "ask" and tool_name not in _CONFIRM_TOOLS:
416
+ _CONFIRM_TOOLS.add(tool_name) # promote for this call only
417
+
418
+ if tool_name not in _CONFIRM_TOOLS:
419
+ return ApprovalDecision.allow()
420
+
421
+ # ── JSON PreToolUse hook — can block execution ────────────────────────────
422
+ if _HAS_JSON_HOOKS and _JSON_HOOKS.get("PreToolUse"):
423
+ _allowed = _fire_json_hook(
424
+ "PreToolUse", tool=tool_name, params=params, hooks=_JSON_HOOKS,
425
+ )
426
+ if not _allowed:
427
+ return ApprovalDecision.deny("Blocked by PreToolUse hook")
428
+
429
+ # ── Pre-flight for run_command ────────────────────────────────────────────
430
+ if tool_name == "run_command":
431
+ from aria_code.safety import classify_command_risk
432
+ cmd = params.get("command", "")
433
+ if isinstance(cmd, list):
434
+ import shlex as _shlex_tmp
435
+ cmd = _shlex_tmp.join(str(c) for c in cmd)
436
+ params["command"] = cmd
437
+ risk = classify_command_risk(cmd)
438
+
439
+ if risk == "high":
440
+ # Always block high-risk regardless of user approval
441
+ if _g("HAS_RICH"):
442
+ _g("console").print(Panel(
443
+ f"[red]✗ 高风险命令已拒绝[/red]\n[dim]{cmd[:120]}[/dim]\n"
444
+ f"[dim]高风险操作(rm -rf / docker / sudo 等)需要在终端手动执行。[/dim]",
445
+ border_style="red", box=rich_box.ROUNDED, padding=(0, 1),
446
+ ))
447
+ else:
448
+ print(f" ✗ 高风险命令已拒绝: {cmd[:80]}")
449
+ return ApprovalDecision.deny("high-risk command")
450
+
451
+ if risk == "medium" and config_policy == "safe":
452
+ # Show a richer picker that includes a "Allow & upgrade policy" option
453
+ if _g("HAS_RICH"):
454
+ _g("console").print()
455
+ _g("console").print(Panel(
456
+ f"[yellow]⚠ 此命令需要 balanced 策略(当前: safe)[/yellow]\n"
457
+ f"[dim]{cmd[:120]}[/dim]",
458
+ border_style="yellow", box=rich_box.ROUNDED, padding=(0, 1),
459
+ ))
460
+ _prefix = _command_approval_prefix(cmd)
461
+ _prefix_label = " ".join(_prefix)[:72] if _prefix else cmd[:72]
462
+ options = [
463
+ ("Allow once", "仅此次允许(不改变策略)"),
464
+ ("Allow similar this session", f"本会话允许前缀: {_prefix_label}"),
465
+ ("Allow & set balanced", "允许并升级策略(本会话有效)"),
466
+ ("No", "拒绝执行"),
467
+ ]
468
+ choice = _arrow_select(options, selected=0, title="")
469
+ if choice == 0:
470
+ return ApprovalDecision.allow(policy="balanced", user_approved=True)
471
+ if choice == 1:
472
+ return ApprovalDecision.allow(
473
+ policy="balanced",
474
+ user_approved=True,
475
+ command_prefix=_prefix,
476
+ )
477
+ if choice == 2:
478
+ return ApprovalDecision.allow(
479
+ policy="balanced",
480
+ user_approved=True,
481
+ upgrade_policy=True,
482
+ )
483
+ return ApprovalDecision.deny("user denied") # No
484
+
485
+ # ── Default confirmation for write_file / edit_file / low-risk run ────────
486
+ if tool_name == "edit_file":
487
+ _show_edit_preview(params)
488
+ elif tool_name == "multi_edit":
489
+ _show_multi_edit_preview(params)
490
+ elif tool_name == "write_file":
491
+ _show_write_preview(params)
492
+ elif tool_name == "run_command":
493
+ # Header already printed by on_tool_call — just pass through policy
494
+ pass
495
+
496
+ _tool_label = {"write_file": "写文件", "edit_file": "编辑文件", "multi_edit": "批量编辑", "run_command": "运行命令"}.get(tool_name, tool_name)
497
+ _command_prefix = _command_approval_prefix(params.get("command", "")) if tool_name == "run_command" else ()
498
+ _scope_label = (
499
+ f"Always allow {' '.join(_command_prefix)[:64]}"
500
+ if _command_prefix else f"Always allow {_tool_label}"
501
+ )
502
+ _scope_help = (
503
+ "本会话内允许相同命令前缀"
504
+ if _command_prefix else f"本会话内自动允许所有 {_tool_label}"
505
+ )
506
+ options = [
507
+ ("Yes", ""),
508
+ (_scope_label, _scope_help),
509
+ ("Yes, allow all tools", "本会话内所有工具自动允许"),
510
+ ("No", ""),
511
+ ]
512
+ choice = _arrow_select(options, selected=0, title="")
513
+
514
+ if choice == 0:
515
+ if tool_name == "run_command":
516
+ return ApprovalDecision.allow(policy=config_policy, user_approved=True)
517
+ return ApprovalDecision.allow()
518
+ if choice == 1:
519
+ return ApprovalDecision.allow(
520
+ policy=config_policy if tool_name == "run_command" else None,
521
+ user_approved=True,
522
+ command_prefix=_command_prefix,
523
+ tool_scope="" if _command_prefix else tool_name,
524
+ )
525
+ if choice == 2:
526
+ if tool_name == "run_command":
527
+ return ApprovalDecision.allow(
528
+ policy=config_policy,
529
+ user_approved=True,
530
+ auto_approve_session=True,
531
+ )
532
+ return ApprovalDecision.allow(auto_approve_session=True)
533
+ return ApprovalDecision.deny("user denied")
534
+ async def execute_aria_tool(base_url: str, tool_name: str, params: dict,
535
+ timeout: int = 30, auth_token: str = None,
536
+ max_retries: int = 2) -> dict:
537
+ """Execute an Aria tool via the backend API with auto-retry and TTL cache."""
538
+ # --- Parameter validation before sending to API ---
539
+ _symbol_tools = {
540
+ "get_market_data", "get_risk_metrics", "calculate_factors",
541
+ "get_alpha158_factors", "assess_portfolio_risk",
542
+ }
543
+ _date_tools = {"backtest_strategy", "stress_test_strategy"}
544
+
545
+ if tool_name in _symbol_tools and "symbol" in params:
546
+ sym = str(params["symbol"]).strip().upper()
547
+ if not re_module.match(r'^[A-Z0-9.\-/=]{1,12}$', sym):
548
+ return {"success": False, "error": f"Invalid symbol format: '{sym}'"}
549
+ params = {**params, "symbol": sym}
550
+
551
+ if tool_name in _date_tools:
552
+ for date_key in ("start_date", "end_date", "start", "end"):
553
+ if date_key in params:
554
+ date_val = str(params[date_key]).strip()
555
+ if not re_module.match(r'^\d{4}-\d{2}-\d{2}$', date_val):
556
+ return {"success": False, "error": f"Invalid date format for '{date_key}': '{date_val}' (expected YYYY-MM-DD)"}
557
+ # Check chronological order
558
+ start = params.get("start_date") or params.get("start")
559
+ end = params.get("end_date") or params.get("end")
560
+ if start and end and start > end:
561
+ return {"success": False, "error": f"start_date ({start}) must be before end_date ({end})"}
562
+
563
+ # Check cache for read-only tools
564
+ ttl = _CACHE_TTL.get(tool_name)
565
+ if ttl:
566
+ cache_key = f"{tool_name}:{json.dumps(params, sort_keys=True)}"
567
+ cached = _TOOL_CACHE.get(cache_key)
568
+ if cached and (time.time() - cached[1]) < ttl:
569
+ return cached[0]
570
+ import aiohttp
571
+ url = f"{base_url}/api/aria/execute-tool"
572
+ payload = {"tool_name": tool_name, "params": params}
573
+ headers = {}
574
+ if auth_token:
575
+ headers["Authorization"] = f"Bearer {auth_token}"
576
+ last_error = None
577
+ for attempt in range(max_retries + 1):
578
+ try:
579
+ async with aiohttp.ClientSession() as session:
580
+ async with session.post(url, json=payload, headers=headers,
581
+ timeout=aiohttp.ClientTimeout(total=timeout)) as resp:
582
+ result = await resp.json()
583
+ if result.get("success") or attempt >= max_retries:
584
+ # Cache successful results for read-only tools
585
+ if result.get("success") and ttl:
586
+ _TOOL_CACHE[cache_key] = (result, time.time())
587
+ return result
588
+ last_error = result.get("error", "Unknown error")
589
+ except Exception as e:
590
+ last_error = str(e)
591
+ if attempt < max_retries:
592
+ await asyncio.sleep(1 * (attempt + 1)) # 1s, 2s backoff
593
+ return {"success": False, "error": f"Failed after {max_retries + 1} attempts: {last_error}"}
594
+ # Hard ceiling on one tool result fed back into the model, in characters.
595
+ # Individual branches below apply their own caps, but several produced
596
+ # unbounded text, and nothing capped the total: a turn could inject tens of
597
+ # thousands of characters of tool output into the context with no limit.
598
+ DEFAULT_TOOL_RESULT_CHAR_LIMIT = 4000
599
+
600
+ # Never truncate below this, or a "summary" degrades into an unusable stub.
601
+ MIN_TOOL_RESULT_CHAR_LIMIT = 400
602
+
603
+
604
+ def truncate_tool_summary(summary: str, limit: int = DEFAULT_TOOL_RESULT_CHAR_LIMIT) -> str:
605
+ """Clip a tool summary to *limit* characters, saying so explicitly.
606
+
607
+ The marker matters: silently dropping the tail makes a model treat a
608
+ partial result as complete. Head and tail are both kept because the useful
609
+ signal in command output and file content sits at the two ends — an error
610
+ traceback ends at the bottom, a file's structure shows at the top.
611
+ """
612
+ text = summary if isinstance(summary, str) else str(summary)
613
+ limit = max(int(limit or 0), MIN_TOOL_RESULT_CHAR_LIMIT)
614
+ if len(text) <= limit:
615
+ return text
616
+ marker_for = (
617
+ lambda n: f"\n\n… [{n} characters truncated to fit the context budget] …\n\n"
618
+ )
619
+ # The marker counts against the budget, so the returned text never exceeds
620
+ # *limit* — a cap that its own notice can overshoot is not a cap.
621
+ budget = max(limit - len(marker_for(len(text))), MIN_TOOL_RESULT_CHAR_LIMIT // 2)
622
+ head = int(budget * 0.7)
623
+ tail = budget - head
624
+ kept = text[:head] + (text[-tail:] if tail > 0 else "")
625
+ return text[:head] + marker_for(len(text) - len(kept)) + (text[-tail:] if tail > 0 else "")
626
+
627
+
628
+ def _format_tool_summary(
629
+ tool_name: str,
630
+ result: dict,
631
+ *,
632
+ char_limit: int = DEFAULT_TOOL_RESULT_CHAR_LIMIT,
633
+ ) -> str:
634
+ """Format a tool result into a concise, length-bounded follow-up summary."""
635
+ return truncate_tool_summary(_format_tool_summary_raw(tool_name, result), char_limit)
636
+
637
+
638
+ def _format_tool_summary_raw(tool_name: str, result: dict) -> str:
639
+ """Format tool result into a concise summary for AI follow-up context."""
640
+ if not result.get("success"):
641
+ return f"Error: {_g('_clean_tool_error_message')(result.get('error', 'failed'))}"
642
+ data = result.get("data", {})
643
+
644
+ # Local finance tools return their payload at the top level, whereas some
645
+ # remote adapters wrap it in ``data``. Treat both shapes consistently.
646
+ # Previously a successful local quote became ``{}`` here, leaving the LLM
647
+ # to invent a price/RSI despite the terminal having displayed real data.
648
+ tool_data = data if isinstance(data, dict) and data else result
649
+ if tool_name == "get_market_data":
650
+ symbol = tool_data.get("symbol", "")
651
+ price = tool_data.get("price", tool_data.get("latest_close"))
652
+ change = tool_data.get("change_pct")
653
+ provider = tool_data.get("provider", "unknown")
654
+ as_of = tool_data.get("as_of")
655
+ retrieved_at = tool_data.get("retrieved_at")
656
+ lines = [f"Market data for {symbol} (provider: {provider})"]
657
+ if price is not None:
658
+ currency = tool_data.get("currency", "")
659
+ lines.append(f"Price: {currency} {price}".strip())
660
+ if change is not None:
661
+ lines.append(f"Change: {change}%")
662
+ if as_of:
663
+ lines.append(f"Provider timestamp: {as_of}")
664
+ elif retrieved_at:
665
+ lines.append(f"Retrieved at: {retrieved_at} (provider did not supply trade timestamp)")
666
+ rsi = tool_data.get("rsi")
667
+ if rsi is not None:
668
+ try:
669
+ rsi_value = float(rsi)
670
+ rsi_label = "oversold" if rsi_value <= 30 else ("overbought" if rsi_value >= 70 else "neutral")
671
+ lines.append(f"RSI(14): {rsi_value:.2f} ({rsi_label})")
672
+ except (TypeError, ValueError):
673
+ lines.append("RSI(14): unavailable")
674
+ macd_hist = tool_data.get("macd_hist")
675
+ if macd_hist is not None:
676
+ lines.append(f"MACD histogram: {macd_hist}")
677
+ lines.append("Use only these values. Do not call this quote 'latest' beyond its stated timestamp.")
678
+ return "\n".join(lines)
679
+
680
+ if tool_name == "analyze_news":
681
+ news = tool_data.get("news") or []
682
+ provider = tool_data.get("provider", "unknown")
683
+ if not news:
684
+ return (
685
+ f"No relevant news returned (provider: {provider}). "
686
+ "Do not infer news sentiment; state that news evidence is unavailable."
687
+ )
688
+ lines = [f"News evidence (provider: {provider}, items: {len(news)}):"]
689
+ for item in news[:3]:
690
+ if not isinstance(item, dict):
691
+ continue
692
+ title = str(item.get("title") or "untitled")[:220]
693
+ published = str(item.get("time") or item.get("published_at") or "time unavailable")
694
+ publisher = str(item.get("publisher") or item.get("source") or "source unavailable")
695
+ lines.append(f"- {title} | {publisher} | {published}")
696
+ return "\n".join(lines)
697
+
698
+ if tool_name == "run_command":
699
+ exit_code = data.get("exit_code", -1)
700
+ stdout = data.get("stdout", "").strip()
701
+ stderr = data.get("stderr", "").strip()
702
+ out = f"exit_code={exit_code}"
703
+ if stdout:
704
+ out += f"\nstdout:\n{stdout[:2000]}"
705
+ if stderr and exit_code != 0:
706
+ out += f"\nstderr:\n{stderr[:500]}"
707
+ # Add actionable hints for common errors
708
+ if exit_code != 0:
709
+ combined = (stdout + " " + stderr).lower()
710
+ combined_raw = stdout + " " + stderr
711
+ if "can't open file" in combined or "no such file" in combined:
712
+ out += "\n\nHINT: The file does not exist. You must create it with write_file first, then run it."
713
+ elif "modulenotfounderror" in combined or "no module named" in combined:
714
+ # Extract module name
715
+ import re as _re
716
+ mod_match = _re.search(r"no module named ['\"]?(\w+)", combined)
717
+ mod_name = mod_match.group(1) if mod_match else "<module_name>"
718
+ out += f"\n\nHINT: Module '{mod_name}' is missing. Fix: run_command pip3 install {mod_name}, then run_command python3 to retry."
719
+ elif "nameerror" in combined:
720
+ # Extract the undefined name
721
+ import re as _re
722
+ name_match = _re.search(r"name ['\"](\w+)['\"] is not defined", combined_raw)
723
+ if name_match:
724
+ missing_name = name_match.group(1)
725
+ out += (f"\n\nHINT: '{missing_name}' is not defined — you forgot to import it. "
726
+ f"Use edit_file to add the missing import (e.g., 'import {missing_name}') at the top of the script, then retry.")
727
+ else:
728
+ out += "\n\nHINT: A variable or module is not defined. Use read_file to check imports, edit_file to add the missing import, then retry."
729
+ elif "syntaxerror" in combined:
730
+ import re as _re
731
+ line_match = _re.search(r"line (\d+)", combined)
732
+ line_hint = f" at line {line_match.group(1)}" if line_match else ""
733
+ out += f"\n\nHINT: Syntax error{line_hint}. Use read_file to see the code, then edit_file to fix the exact line, then retry."
734
+ elif "typeerror" in combined:
735
+ out += "\n\nHINT: Type error — wrong argument types or wrong number of arguments. Use read_file to inspect, edit_file to fix, then retry."
736
+ elif "keyerror" in combined or "indexerror" in combined:
737
+ # Special hint for yfinance MultiIndex KeyError
738
+ if any(col in combined_raw for col in ("'Close'", "'Open'", "'High'", "'Low'", "'Volume'")):
739
+ out += ("\n\nHINT: yfinance MultiIndex KeyError — yf.download() returns MultiIndex columns "
740
+ "when downloading multiple tickers. Fix: add `if isinstance(df.columns, pd.MultiIndex): "
741
+ "df.columns = df.columns.droplevel(1)` right after yf.download(). "
742
+ "Use edit_file to add this fix, then retry.")
743
+ else:
744
+ out += "\n\nHINT: Data structure mismatch. Use read_file to check the code logic. The data may have different column names or fewer elements than expected."
745
+ elif "attributeerror" in combined:
746
+ out += "\n\nHINT: Attribute error — the object doesn't have that method/property. Check the library version or API docs. Use read_file then edit_file to fix."
747
+ elif "valueerror" in combined:
748
+ out += "\n\nHINT: Value error — invalid value passed to a function. Use read_file to check the data types and fix with edit_file."
749
+ elif "permission denied" in combined:
750
+ out += "\n\nHINT: Permission denied. Try adding chmod +x, or run with python3 explicitly."
751
+ else:
752
+ out += "\n\nHINT: Script failed. Use read_file to inspect the code, find the error, edit_file to fix it, then run_command to retry. Do NOT give up."
753
+ else:
754
+ # Script succeeded — auto-verify and auto-open output files.
755
+ from aria_code.artifacts import user_generated_dir as _aria_user_generated_dir
756
+ output_dir = _aria_user_generated_dir()
757
+ try:
758
+ recent_files = []
759
+ for ext in ("*.png", "*.html", "*.csv", "*.pdf", "*.xlsx"):
760
+ for f in output_dir.rglob(ext):
761
+ if (time.time() - f.stat().st_mtime) < 30:
762
+ recent_files.append(f)
763
+ # Also detect files mentioned in stdout (e.g., "Saved to /path/to/file.png")
764
+ saved_pattern = re_module.findall(r'(?:saved?\s+(?:to|as|at)|wrote|output|created)[:\s]+([^\s\'"]+\.(?:png|html|csv|pdf))', stdout, re_module.IGNORECASE)
765
+ for sp in saved_pattern:
766
+ p = pathlib.Path(sp).expanduser().resolve()
767
+ if p.exists() and p not in recent_files:
768
+ recent_files.append(p)
769
+ if recent_files:
770
+ names = [f.name for f in recent_files]
771
+ out += f"\n\nVerified: output files created: {', '.join(names)}"
772
+ # Auto-open on macOS (non-blocking)
773
+ for f in recent_files[:3]:
774
+ try:
775
+ subprocess.Popen(["open", str(f)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
776
+ except Exception:
777
+ pass
778
+ if _g("HAS_RICH"):
779
+ _g("console").print(f" [dim]Opened {', '.join(names[:3])}[/dim]")
780
+ else:
781
+ combined_check = (stdout + " " + stderr).lower()
782
+ if any(kw in combined_check for kw in ("chart", "plot", "figure", "savefig", "save")):
783
+ out += ("\n\nWARNING: Script ran but no output files detected in the Aria generated output directory. "
784
+ "Check the save path uses ~/Documents/Aria Code/generated.")
785
+ except Exception:
786
+ pass
787
+ return out
788
+ if tool_name == "write_file":
789
+ _base = f"OK: {data.get('action', 'created')} {data.get('path', '')} ({data.get('lines', 0)} lines)"
790
+ if result.get("warning"):
791
+ _base += f"\n\n{result['warning']}"
792
+ return _base
793
+ if tool_name == "edit_file":
794
+ _base = f"OK: edited {data.get('path', '')} ({data.get('replacements', 0)} replacements)"
795
+ if result.get("warning"):
796
+ _base += f"\n\n{result['warning']}"
797
+ return _base
798
+ if tool_name == "multi_edit":
799
+ _base = f"OK: {data.get('edits_applied', 0)} edits applied to {data.get('path', '')}"
800
+ if result.get("warning"):
801
+ _base += f"\n\n{result['warning']}"
802
+ return _base
803
+ if tool_name == "update_todos":
804
+ _todos = data.get("todos", [])
805
+ _lines = [f"任务进度 {data.get('completed', 0)}/{data.get('total', len(_todos))}:"]
806
+ _mark = {"completed": "[x]", "in_progress": "[>]", "pending": "[ ]"}
807
+ for _t in _todos:
808
+ _lines.append(f" {_mark.get(_t.get('status'), '[ ]')} {_t.get('content', '')}")
809
+ return "\n".join(_lines)
810
+ if tool_name == "read_file":
811
+ content = data.get("content", "")
812
+ return f"OK: {data.get('lines', 0)} lines\n{content[:2000]}"
813
+ if tool_name == "list_files":
814
+ items = data.get("items", [])
815
+ names = [it["name"] if isinstance(it, dict) else str(it) for it in items[:20]]
816
+ return f"OK: {data.get('count', len(items))} items: {', '.join(names)}"
817
+ if tool_name == "search_code":
818
+ matches = data.get("matches", [])
819
+ return f"OK: {len(matches)} matches\n" + "\n".join(str(m)[:200] for m in matches[:10])
820
+ # Remote tools — JSON summary
821
+ return json.dumps(data, ensure_ascii=False)[:2000]
822
+
823
+ __all__ = ['_g', '_tool_analyze_file', '_tool_read_file', '_strip_markdown_fences', '_auto_fix_python', '_write_policy_confirm', '_tool_write_file', '_tool_edit_file', '_tool_multi_edit', '_tool_update_todos', '_tool_list_files', '_tool_search_code', '_tool_run_command', '_tool_web_fetch', '_tool_github', '_tool_glob', '_tool_notebook_read', '_tool_notebook_edit', '_tool_broker_query', '_tool_broker_order', '_tool_get_market_data', '_tool_get_market_history', '_todo_schema', '_wrap_bare_schemas', '_dedup_tool_schemas', '_show_edit_preview', '_show_multi_edit_preview', '_show_write_preview', '_apply_tool_approval', '_confirm_tool_execution_decision', 'execute_aria_tool', '_format_tool_summary', '_format_tool_summary_raw', 'truncate_tool_summary', 'DEFAULT_TOOL_RESULT_CHAR_LIMIT', 'MIN_TOOL_RESULT_CHAR_LIMIT']