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,657 @@
1
+ """CLI consumers for runtime/model events.
2
+
3
+ The runtime core emits typed events and asks for approval decisions. This
4
+ module keeps terminal-specific rendering and prompts at the CLI adapter layer
5
+ instead of letting them spread through the agent loop.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ import sys
12
+ import time
13
+ from dataclasses import dataclass, field
14
+ from enum import Enum
15
+ from pathlib import Path
16
+ from typing import Any, Callable
17
+
18
+ from aria_code.runtime import (
19
+ AgentEventStatus,
20
+ AgentEventThinking,
21
+ AgentEventToken,
22
+ AgentEventToolCall,
23
+ AgentEventToolResult,
24
+ ApprovalDecision,
25
+ )
26
+
27
+
28
+ _REPETITION_MARKER = "*[model stopped — repetition detected]*"
29
+ _REPETITION_NOTICE = (
30
+ "\n\n> 已检测到模型开始重复输出,已自动停止展开。"
31
+ "上方结果仍然有效;如需继续,请指定要补充的部分。"
32
+ )
33
+
34
+
35
+ def _redact_activity_text(value: Any, limit: int = 90) -> str:
36
+ text = re.sub(r"\s+", " ", str(value or "")).strip()
37
+ text = re.sub(
38
+ r"(?i)(api[_-]?key|token|password|secret)(\s*[=:]\s*|\s+)[^\s]+",
39
+ r"\1\2***",
40
+ text,
41
+ )
42
+ return text[:limit]
43
+
44
+
45
+ def _tool_activity_hint(tool: str, params: dict[str, Any]) -> str:
46
+ """Return one safe, useful argument for a compact activity row."""
47
+ if not isinstance(params, dict):
48
+ return ""
49
+ for key in ("path", "file_path", "filename", "file"):
50
+ if params.get(key):
51
+ return Path(str(params[key])).name[:90]
52
+ for key in ("symbol", "ticker", "query", "q", "command", "url", "task"):
53
+ if params.get(key):
54
+ return _redact_activity_text(params[key])
55
+ title = params.get("title")
56
+ return _redact_activity_text(title) if title else ""
57
+
58
+
59
+ class TurnPhase(str, Enum):
60
+ """User-visible lifecycle for one assistant turn."""
61
+
62
+ CONNECTING = "connecting"
63
+ THINKING = "thinking"
64
+ STREAMING = "streaming"
65
+ TOOL = "tool"
66
+ DONE = "done"
67
+ ERROR = "error"
68
+ CANCELLED = "cancelled"
69
+
70
+
71
+ @dataclass
72
+ class TurnLifecycle:
73
+ phase: TurnPhase = TurnPhase.CONNECTING
74
+ started_at: float = field(default_factory=time.time)
75
+ first_visible_at: float | None = None
76
+ ended_at: float | None = None
77
+
78
+ def transition(self, phase: TurnPhase) -> None:
79
+ self.phase = phase
80
+ now = time.time()
81
+ if phase is TurnPhase.STREAMING and self.first_visible_at is None:
82
+ self.first_visible_at = now
83
+ if phase in {TurnPhase.DONE, TurnPhase.ERROR, TurnPhase.CANCELLED}:
84
+ self.ended_at = now
85
+
86
+
87
+ class TerminalRuntimeEventConsumer:
88
+ """Consume runtime/provider events and render them to a terminal."""
89
+
90
+ def __init__(
91
+ self,
92
+ *,
93
+ terminal: Any,
94
+ console: Any,
95
+ has_rich: bool,
96
+ markdown_cls: type | None,
97
+ live_cls: type | None,
98
+ strip_latex: Callable[[str], str],
99
+ set_robot_state: Callable[[Any], None] | None = None,
100
+ streaming_state: Any = None,
101
+ print_tool_call: Callable[[str, dict], None] | None = None,
102
+ print_tool_done: Callable[..., None] | None = None,
103
+ fallback_from: str = "local",
104
+ live_update_interval: float = 0.08,
105
+ ui_lang: str = "en",
106
+ on_response_start: Callable[[], None] | None = None,
107
+ on_phase_change: Callable[[TurnPhase], None] | None = None,
108
+ ) -> None:
109
+ self.terminal = terminal
110
+ self.console = console
111
+ self.has_rich = has_rich
112
+ self.markdown_cls = markdown_cls
113
+ self.live_cls = live_cls
114
+ self.strip_latex = strip_latex
115
+ self.set_robot_state = set_robot_state
116
+ self.streaming_state = streaming_state
117
+ self.print_tool_call = print_tool_call
118
+ self.print_tool_done = print_tool_done
119
+ self.fallback_from = fallback_from
120
+ self.live_update_interval = live_update_interval
121
+ self.ui_lang = ui_lang or "en"
122
+ self.on_response_start = on_response_start
123
+ self.on_phase_change = on_phase_change
124
+ self.lifecycle = TurnLifecycle()
125
+
126
+ self.response_text = ""
127
+ self.streamed_any = False
128
+ self.token_count = 0
129
+ self.thinking_tokens = 0
130
+ self.thinking_shown = False
131
+ self.thinking_start: float | None = None
132
+ self.thinking_finished = False
133
+ self.thinking_preview_buf: list[str] = []
134
+ self.thinking_full_buf: list[str] = []
135
+ self.tool_start_times: dict[str, list[float]] = {}
136
+ self.tool_params: dict[str, list[dict[str, Any]]] = {}
137
+ self.repetition_stopped = False
138
+ self.repetition_notice_printed = False
139
+ self.response_started = False
140
+
141
+ self.live_display = None
142
+ self.spinner = None
143
+ self.first_token_received_ref = [False]
144
+ self.token_start_time: float | None = None
145
+ self.last_live_update = 0.0
146
+ self.use_plain_print_ref = [False]
147
+ self.use_batch_render_ref = [False]
148
+ self.latex_buf_ref = [""]
149
+ self.in_latex_ref = [False]
150
+
151
+ @property
152
+ def first_token_received(self) -> bool:
153
+ return self.first_token_received_ref[0]
154
+
155
+ @first_token_received.setter
156
+ def first_token_received(self, value: bool) -> None:
157
+ self.first_token_received_ref[0] = value
158
+
159
+ @property
160
+ def use_plain_print(self) -> bool:
161
+ return self.use_plain_print_ref[0]
162
+
163
+ @use_plain_print.setter
164
+ def use_plain_print(self, value: bool) -> None:
165
+ self.use_plain_print_ref[0] = value
166
+
167
+ @property
168
+ def use_batch_render(self) -> bool:
169
+ return self.use_batch_render_ref[0]
170
+
171
+ @use_batch_render.setter
172
+ def use_batch_render(self, value: bool) -> None:
173
+ self.use_batch_render_ref[0] = value
174
+
175
+ @property
176
+ def latex_buf(self) -> str:
177
+ return self.latex_buf_ref[0]
178
+
179
+ @latex_buf.setter
180
+ def latex_buf(self, value: str) -> None:
181
+ self.latex_buf_ref[0] = value
182
+
183
+ @property
184
+ def in_latex(self) -> bool:
185
+ return self.in_latex_ref[0]
186
+
187
+ @in_latex.setter
188
+ def in_latex(self, value: bool) -> None:
189
+ self.in_latex_ref[0] = value
190
+
191
+ def start_spinner(self) -> None:
192
+ self.set_phase(TurnPhase.THINKING)
193
+ if self.has_rich and self.spinner is None and not self.first_token_received:
194
+ label = "思考中" if self.ui_lang.lower().startswith("zh") else "Thinking"
195
+ cancel = "esc 取消" if self.ui_lang.lower().startswith("zh") else "esc cancel"
196
+ self.spinner = self.console.status(
197
+ f"[dim]{label}… [/dim][dim italic]{cancel}[/dim italic]",
198
+ spinner="dots",
199
+ spinner_style="dim",
200
+ )
201
+ self.spinner.__enter__()
202
+
203
+ def set_phase(self, phase: TurnPhase) -> None:
204
+ if self.lifecycle.phase is phase:
205
+ return
206
+ self.lifecycle.transition(phase)
207
+ if self.on_phase_change is not None:
208
+ self.on_phase_change(phase)
209
+
210
+ def ensure_response_started(self) -> None:
211
+ """Render the response label only when visible answer text exists."""
212
+ if self.response_started:
213
+ return
214
+ self._finish_thinking()
215
+ self.stop_spinner()
216
+ self.response_started = True
217
+ if self.on_response_start is not None:
218
+ self.on_response_start()
219
+
220
+ def finish(self, phase: TurnPhase = TurnPhase.DONE) -> None:
221
+ self._finish_thinking()
222
+ self.stop_spinner()
223
+ self.set_phase(phase)
224
+
225
+ def stop_spinner(self) -> None:
226
+ if self.spinner is not None:
227
+ try:
228
+ self.spinner.__exit__(None, None, None)
229
+ except Exception:
230
+ pass
231
+ self.spinner = None
232
+
233
+ def stop_live(self, discard: bool = False) -> None:
234
+ self.stop_spinner()
235
+ if self.live_display:
236
+ try:
237
+ if discard:
238
+ try:
239
+ from rich.text import Text as _RichText
240
+
241
+ self.live_display.update(_RichText(""))
242
+ self.live_display.refresh()
243
+ except Exception:
244
+ pass
245
+ self.live_display.stop()
246
+ except Exception:
247
+ pass
248
+ self.live_display = None
249
+ elif self.first_token_received and not discard and not self.use_batch_render:
250
+ print(flush=True)
251
+
252
+ def set_batch_render_mode(self, enabled: bool = True) -> None:
253
+ self.use_plain_print = enabled
254
+ self.use_batch_render = enabled
255
+
256
+ def reset_stream_state(self) -> None:
257
+ self.response_text = ""
258
+ self.streamed_any = False
259
+ self.token_count = 0
260
+ self.first_token_received = False
261
+ self.token_start_time = None
262
+ self.latex_buf = ""
263
+ self.in_latex = False
264
+
265
+ def flush_latex_buf(self) -> str:
266
+ raw = self.latex_buf
267
+ self.latex_buf = ""
268
+ self.in_latex = False
269
+ return self.strip_latex(raw) if raw.strip() else raw
270
+
271
+ def _show_repetition_notice(self) -> None:
272
+ if self.repetition_notice_printed or self.use_batch_render:
273
+ return
274
+ self.repetition_notice_printed = True
275
+ if self.live_display and self.has_rich and self.markdown_cls is not None:
276
+ clean_text = self.response_text.split(_REPETITION_MARKER, 1)[0].rstrip()
277
+ self.live_display.update(self.markdown_cls(self.strip_latex(clean_text + _REPETITION_NOTICE)))
278
+ self.live_display.refresh()
279
+ return
280
+ print(_REPETITION_NOTICE, end="", flush=True)
281
+
282
+ def finalize_text(self, final_text: str) -> str:
283
+ if self.in_latex and self.latex_buf:
284
+ leftover = self.flush_latex_buf()
285
+ final_text = (final_text or "") + leftover
286
+ if self.use_plain_print and not self.use_batch_render:
287
+ print(leftover, end="", flush=True)
288
+ return final_text
289
+
290
+ def _finish_thinking(self) -> None:
291
+ if not self.thinking_shown or self.thinking_finished:
292
+ return
293
+ self.thinking_finished = True
294
+ self.stop_spinner()
295
+ elapsed_t = time.time() - self.thinking_start if self.thinking_start else 0
296
+ self.terminal._last_thinking = "".join(self.thinking_full_buf).strip()
297
+ is_zh = self.ui_lang.lower().startswith("zh")
298
+ t_info = f"思考 {elapsed_t:.1f}s" if is_zh else f"Thought for {elapsed_t:.1f}s"
299
+ if self.thinking_tokens > 0:
300
+ t_info += f" · {self.thinking_tokens:,} tokens"
301
+ expand = "展开" if is_zh else "expand"
302
+ ctrlo = f" [dim]· Ctrl+O {expand}[/dim]" if self.terminal._last_thinking else ""
303
+ if self.has_rich:
304
+ sys.stdout.write("\r\033[K")
305
+ sys.stdout.flush()
306
+ self.console.print(f" [dim]✻[/dim] [dim]{t_info}[/dim]{ctrlo}")
307
+ if self.terminal.config.get("thinking_preview") and self.thinking_preview_buf:
308
+ preview_text = "".join(self.thinking_preview_buf)[:280].strip()
309
+ if len("".join(self.thinking_preview_buf)) > 280:
310
+ preview_text += "…"
311
+ self.console.print(f" [dim italic]{preview_text}[/dim italic]")
312
+ else:
313
+ print(f"\r ✻ {t_info}")
314
+
315
+ def on_token(self, token: str) -> None:
316
+ if "<|im_start|>" in token or "<|im_end|>" in token:
317
+ token = token.replace("<|im_start|>", "").replace("<|im_end|>", "")
318
+ if not token.strip():
319
+ return
320
+
321
+ meta_artifacts = (
322
+ "(注释:",
323
+ "(注释:",
324
+ "(提示:",
325
+ "(提示:",
326
+ "请使用实际注入的数据",
327
+ "请使用实际数据",
328
+ "实际注入的数据",
329
+ "[system]",
330
+ "[/system]",
331
+ "[INST]",
332
+ "[/INST]",
333
+ )
334
+ if any(a in token for a in meta_artifacts):
335
+ token = re.sub(
336
+ r"\(注[释释]:[^))]*[))]|(注[释释]:[^))]*[))]"
337
+ r"|\(提示:[^))]*[))]|(提示:[^))]*[))]"
338
+ r"|请使用实际(?:注入的)?数据[^。\n]*"
339
+ r"|\[/?(?:system|INST)\]",
340
+ "",
341
+ token,
342
+ )
343
+ if not token.strip():
344
+ return
345
+
346
+ if not self.first_token_received and not token.strip():
347
+ self.response_text += token
348
+ self.streamed_any = True
349
+ self.token_count += 1
350
+ return
351
+
352
+ if not self.first_token_received:
353
+ self.first_token_received = True
354
+ self.token_start_time = time.time()
355
+ self.set_phase(TurnPhase.STREAMING)
356
+ if self.set_robot_state is not None:
357
+ self.set_robot_state(self.streaming_state)
358
+ if not self.use_batch_render:
359
+ self.ensure_response_started()
360
+
361
+ if _REPETITION_MARKER in token:
362
+ if self.use_batch_render:
363
+ self.response_text += token
364
+ self.streamed_any = True
365
+ self.token_count += 1
366
+ self.repetition_stopped = True
367
+ return
368
+ before, _, _after = token.partition(_REPETITION_MARKER)
369
+ if before:
370
+ self.on_token(before)
371
+ self.response_text += _REPETITION_MARKER
372
+ self.streamed_any = True
373
+ self.token_count += 1
374
+ self.repetition_stopped = True
375
+ self._show_repetition_notice()
376
+ return
377
+
378
+ self._finish_thinking()
379
+
380
+ if self.use_batch_render:
381
+ self.response_text += token
382
+ self.streamed_any = True
383
+ self.token_count += 1
384
+ return
385
+
386
+ open_delims = (r"\(", r"\[", "$$")
387
+ close_delims = (r"\)", r"\]", "$$")
388
+ if not self.in_latex:
389
+ if any(d in token for d in open_delims):
390
+ self.in_latex = True
391
+ self.latex_buf = token
392
+ tail = token
393
+ for od, cd in zip(open_delims, close_delims):
394
+ if od in tail:
395
+ after = tail[tail.index(od) + len(od):]
396
+ if cd in after:
397
+ token = self.flush_latex_buf()
398
+ break
399
+ else:
400
+ self.response_text += self.latex_buf
401
+ self.streamed_any = True
402
+ self.token_count += 1
403
+ return
404
+ else:
405
+ token = self.strip_latex(token)
406
+ else:
407
+ self.latex_buf += token
408
+ if any(d in token for d in close_delims):
409
+ token = self.flush_latex_buf()
410
+ else:
411
+ self.response_text += token
412
+ self.streamed_any = True
413
+ self.token_count += 1
414
+ return
415
+
416
+ self.response_text += token
417
+ self.streamed_any = True
418
+ self.token_count += 1
419
+ can_live = (
420
+ self.has_rich
421
+ and not self.use_plain_print
422
+ and getattr(self.console, "is_terminal", False)
423
+ and not getattr(self.console, "is_dumb_terminal", True)
424
+ and self.markdown_cls is not None
425
+ and self.live_cls is not None
426
+ )
427
+ if can_live:
428
+ now = time.time()
429
+ md = self.markdown_cls(self.strip_latex(self.response_text))
430
+ if self.live_display is None:
431
+ self.live_display = self.live_cls(
432
+ md,
433
+ console=self.console,
434
+ refresh_per_second=12,
435
+ vertical_overflow="visible",
436
+ )
437
+ self.live_display.start()
438
+ self.last_live_update = now
439
+ elif now - self.last_live_update >= self.live_update_interval:
440
+ self.live_display.update(md)
441
+ self.last_live_update = now
442
+ else:
443
+ print(token, end="", flush=True)
444
+
445
+ def on_thinking(self, content: str) -> None:
446
+ self.set_phase(TurnPhase.THINKING)
447
+ if self.thinking_finished:
448
+ # 工具调用后的新一段思考:重置段状态,否则上一段的 finished 闩锁
449
+ # 让本段永远无法 finalize,"思考中 …(N tokens)"活行被冻结在滚动区
450
+ # (即多条思考行并存的根因)。full_buf 不清,Ctrl+O 展开保留全部段落。
451
+ self.thinking_finished = False
452
+ self.thinking_shown = False
453
+ self.thinking_tokens = 0
454
+ self.thinking_preview_buf = []
455
+ if not self.thinking_shown:
456
+ self.stop_spinner()
457
+ self.thinking_start = time.time()
458
+ self.thinking_shown = True
459
+ self.thinking_tokens += 1
460
+ if self.thinking_tokens % 30 == 1:
461
+ elapsed = time.time() - self.thinking_start
462
+ label = "思考中" if self.ui_lang.lower().startswith("zh") else "Thinking"
463
+ # 与 finalize 行(思考 Xs · N tokens)同格式,避免括号/点分隔两种样式并存
464
+ sys.stdout.write(
465
+ f"\r \033[2m✻\033[0m \033[2m{label} {elapsed:.1f}s"
466
+ f" · {self.thinking_tokens} tokens\033[0m "
467
+ )
468
+ sys.stdout.flush()
469
+ if len("".join(self.thinking_preview_buf)) < 300:
470
+ self.thinking_preview_buf.append(content)
471
+ if len("".join(self.thinking_full_buf)) < 8000:
472
+ self.thinking_full_buf.append(content)
473
+
474
+ def on_tool_call(self, tool: str, params: dict) -> None:
475
+ self.set_phase(TurnPhase.TOOL)
476
+ self._finish_thinking()
477
+
478
+ # Cursor/Windsurf 风格的实时加载动画
479
+ if self.has_rich and hasattr(self, 'console'):
480
+ hint = _tool_activity_hint(tool, params)
481
+ label = f"[bold cyan]调用工具[/bold cyan] [green]{tool}[/green]"
482
+ if hint:
483
+ label += f" [dim]({hint})[/dim]"
484
+ self.tool_spinner = self.console.status(label, spinner="dots12", spinner_style="cyan")
485
+ self.tool_spinner.__enter__()
486
+
487
+ if self.print_tool_call is not None:
488
+ self.print_tool_call(tool, params if isinstance(params, dict) else {})
489
+
490
+ self.tool_start_times.setdefault(tool, []).append(time.time())
491
+ safe_params = dict(params) if isinstance(params, dict) else {}
492
+ self.tool_params.setdefault(tool, []).append(safe_params)
493
+
494
+ def on_tool_result(self, tool: str, summary: Any) -> None:
495
+ # 关闭工具加载动画
496
+ if hasattr(self, 'tool_spinner') and self.tool_spinner is not None:
497
+ try:
498
+ self.tool_spinner.__exit__(None, None, None)
499
+ except Exception:
500
+ pass
501
+ self.tool_spinner = None
502
+
503
+ starts = self.tool_start_times.get(tool) or []
504
+ started_at = starts.pop(0) if starts else time.time()
505
+ if not starts:
506
+ self.tool_start_times.pop(tool, None)
507
+ elapsed_ms = int((time.time() - started_at) * 1000)
508
+ params_queue = self.tool_params.get(tool) or []
509
+ params = params_queue.pop(0) if params_queue else {}
510
+ if not params_queue:
511
+ self.tool_params.pop(tool, None)
512
+ ok = not (isinstance(summary, dict) and not summary.get("success", True))
513
+ if self.print_tool_done is not None:
514
+ # Surface the failure reason on the ✗ line — a red cross with no
515
+ # explanation leaves both the user and the reviewer blind to WHY
516
+ # (observed in the channels drill: peer_comparison failed 4× with
517
+ # nothing on screen).
518
+ detail = ""
519
+ if not ok and isinstance(summary, dict):
520
+ detail = str(summary.get("error") or "")[:90]
521
+ self.print_tool_done(tool, elapsed_ms, success=ok, summary=detail)
522
+
523
+ ts = time.strftime("%H:%M:%S")
524
+ icon = "✓" if ok else "✗"
525
+ hint = _tool_activity_hint(tool, params)
526
+ duration = f"{elapsed_ms}ms" if elapsed_ms < 1000 else f"{elapsed_ms / 1000:.1f}s"
527
+ entry = f"{ts} {icon} {tool}"
528
+ if hint:
529
+ entry += f" {hint}"
530
+ entry += f" · {duration}"
531
+ if not ok and isinstance(summary, dict):
532
+ error = _redact_activity_text(summary.get("error"), limit=100)
533
+ if error:
534
+ entry += f" · {error}"
535
+ self.terminal._transcript_log.append(entry)
536
+ if len(self.terminal._transcript_log) > 100:
537
+ self.terminal._transcript_log = self.terminal._transcript_log[-100:]
538
+
539
+ if tool in ("TaskCreate", "TaskUpdate") and isinstance(summary, dict):
540
+ tid = summary.get("id") or summary.get("task_id")
541
+ title = summary.get("title", "")
542
+ status = summary.get("status", "pending")
543
+ if tid:
544
+ existing = next((t for t in self.terminal._task_list if t.get("id") == tid), None)
545
+ if existing:
546
+ existing["status"] = status
547
+ if title:
548
+ existing["title"] = title
549
+ else:
550
+ self.terminal._task_list.append({"id": tid, "title": title, "status": status})
551
+
552
+ def on_status(self, state: str, message: str) -> None:
553
+ if state in ("acceptance_passed", "acceptance_failed"):
554
+ # The gate's verdict is the one piece of evidence behind "done", so
555
+ # it is printed rather than left in the turn result: a user who
556
+ # cannot see that the check went red will read the model's summary
557
+ # as if it had gone green.
558
+ passed = state == "acceptance_passed"
559
+ if self.has_rich and self.console is not None:
560
+ colour = "green" if passed else "yellow"
561
+ self.console.print(f" [{colour}]{'✓' if passed else '✗'} {message}[/{colour}]")
562
+ else:
563
+ print(f" {'✓' if passed else '✗'} {message}")
564
+ return
565
+ if state != "fallback":
566
+ return
567
+ match = re.search(r"(?:from\s+)?(\w+)\s*(?:→|->|to)\s*(\w+)", message or "", re.I)
568
+ if match:
569
+ from_provider, to_provider = match.group(1), match.group(2)
570
+ else:
571
+ from_provider, to_provider = self.fallback_from, "cloud"
572
+ from aria_code.ui.render.output import print_fallback_toast
573
+
574
+ print_fallback_toast(
575
+ from_provider,
576
+ to_provider,
577
+ message or "",
578
+ console=self.console,
579
+ has_rich=self.has_rich,
580
+ )
581
+
582
+ def handle_runtime_event(self, event: Any) -> None:
583
+ if isinstance(event, AgentEventToken):
584
+ self.on_token(event.text)
585
+ elif isinstance(event, AgentEventThinking):
586
+ self.on_thinking(event.content)
587
+ elif isinstance(event, AgentEventToolCall):
588
+ self.on_tool_call(event.tool, event.params)
589
+ elif isinstance(event, AgentEventToolResult):
590
+ self.on_tool_result(event.tool, event.result)
591
+ elif isinstance(event, AgentEventStatus):
592
+ self.on_status(event.state, event.message)
593
+
594
+
595
+ class TerminalApprovalEventConsumer:
596
+ """Terminal-side approval prompt consumer for runtime tool execution."""
597
+
598
+ def __init__(
599
+ self,
600
+ *,
601
+ terminal: Any,
602
+ console: Any,
603
+ has_rich: bool,
604
+ confirm_decision: Callable[..., ApprovalDecision],
605
+ apply_decision: Callable[[dict, ApprovalDecision], dict],
606
+ save_config: Callable[[dict], None],
607
+ ) -> None:
608
+ self.terminal = terminal
609
+ self.console = console
610
+ self.has_rich = has_rich
611
+ self.confirm_decision = confirm_decision
612
+ self.apply_decision = apply_decision
613
+ self.save_config = save_config
614
+
615
+ async def approve(
616
+ self,
617
+ tool_name: str,
618
+ tool_params: dict,
619
+ *,
620
+ stop_before_prompt: Callable[[], None] | None = None,
621
+ ) -> ApprovalDecision:
622
+ if stop_before_prompt is not None:
623
+ stop_before_prompt()
624
+ try:
625
+ approval = self.confirm_decision(
626
+ tool_name,
627
+ tool_params,
628
+ config_policy=self.terminal.config.get("command_policy", "safe"),
629
+ )
630
+ except KeyboardInterrupt:
631
+ approval = ApprovalDecision.deny("KeyboardInterrupt")
632
+
633
+ self.terminal._record_feedback(
634
+ "tool_accept" if approval.approved else "tool_reject",
635
+ tool_name,
636
+ )
637
+ if not approval.approved:
638
+ from aria_code.ui.render.output import print_tool_blocked
639
+
640
+ print_tool_blocked(tool_name, "用户取消", console=self.console, has_rich=self.has_rich)
641
+ return approval
642
+
643
+ def apply(self, tool_params: dict, approval: ApprovalDecision) -> dict:
644
+ self.apply_decision(tool_params, approval)
645
+ if approval.upgrade_policy:
646
+ tool_params.pop("_upgrade_policy", None)
647
+ self.terminal.config["command_policy"] = "balanced"
648
+ try:
649
+ self.save_config(self.terminal.config)
650
+ if self.has_rich:
651
+ self.console.print(" [dim]策略已升级为 balanced 并保存[/dim]")
652
+ except Exception:
653
+ pass
654
+ return tool_params
655
+
656
+
657
+ __all__ = ["TerminalApprovalEventConsumer", "TerminalRuntimeEventConsumer"]
@@ -0,0 +1,32 @@
1
+ """Sandbox execution module for Aria Code."""
2
+ import subprocess
3
+ import os
4
+
5
+ def run_in_docker_sandbox(command: str, cwd: str = None, timeout: int = 120, image: str = "python:3.11-slim") -> subprocess.CompletedProcess:
6
+ """Run a shell command securely inside an isolated Docker container.
7
+
8
+ This mounts the current workspace so scripts can operate on files,
9
+ but prevents access to the host's system files and network interfaces
10
+ if configured with --network none (optional).
11
+ """
12
+ workspace = cwd or os.getcwd()
13
+
14
+ # We use docker run with a volume mount to the workspace.
15
+ # --rm: remove container after exit
16
+ # -v: mount workspace to /workspace
17
+ # -w: set working directory to /workspace
18
+ docker_cmd = [
19
+ "docker", "run", "--rm",
20
+ "-v", f"{workspace}:/workspace",
21
+ "-w", "/workspace",
22
+ # "--network", "none", # Uncomment to completely isolate network
23
+ image,
24
+ "sh", "-c", command
25
+ ]
26
+
27
+ return subprocess.run(
28
+ docker_cmd,
29
+ capture_output=True,
30
+ text=True,
31
+ timeout=timeout
32
+ )