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,775 @@
1
+ """Generic tool-result and error rendering for Aria Code.
2
+
3
+ All functions accept console / has_rich as parameters so they stay
4
+ import-free from aria_cli.py and testable in isolation.
5
+
6
+ Public surface
7
+ --------------
8
+ FINANCE_TOOL_NAMES frozenset of tool names with dedicated renderers
9
+ clean_tool_error_message(e) short user-facing string from any exception
10
+ error_hint(msg, context) actionable recovery suggestion
11
+ print_error(msg, context, *, console, has_rich, rich_box)
12
+ print_tool_result(...)
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import difflib
18
+ import pathlib
19
+ import re
20
+ import time
21
+
22
+
23
+ # ── Finance tool name registry ─────────────────────────────────────────────────
24
+
25
+ FINANCE_TOOL_NAMES: frozenset = frozenset({
26
+ "get_market_data", "get_market_history", "get_crypto_data", "get_forex_data",
27
+ "get_commodities_data", "get_futures_data", "calculate_factors",
28
+ "backtest_strategy", "cloud_backtest", "run_portfolio_backtest", "get_risk_metrics",
29
+ "optimize_positions", "get_sector_performance", "get_northbound_flow",
30
+ "screen_ashare", "get_limit_up_pool", "get_market_indices",
31
+ "analyze_news", "get_bonds_data", "get_ai_signal",
32
+ "get_market_insights", "get_predictions",
33
+ "broker_query", "broker_order",
34
+ })
35
+
36
+
37
+ # ── Narrow-terminal Markdown adaptation ──────────────────────────────────────
38
+
39
+ _TABLE_SEPARATOR_CELL = re.compile(r"^:?-{3,}:?$")
40
+
41
+
42
+ def _markdown_table_cells(line: str) -> list[str]:
43
+ """Split one simple GFM table row without leaking surrounding pipes."""
44
+ stripped = line.strip()
45
+ if not stripped.startswith("|"):
46
+ return []
47
+ return [cell.strip() for cell in stripped.strip("|").split("|")]
48
+
49
+
50
+ def _is_markdown_table_separator(line: str) -> bool:
51
+ cells = _markdown_table_cells(line)
52
+ return bool(cells) and all(_TABLE_SEPARATOR_CELL.fullmatch(cell) for cell in cells)
53
+
54
+
55
+ def adapt_markdown_for_width(markup: str, width: int, *, table_breakpoint: int = 96) -> str:
56
+ """Convert GFM tables to stacked records when the terminal is narrow.
57
+
58
+ Rich correctly renders tables, but a three-column research table inside an
59
+ 80-column terminal has too little room for Chinese prose. Cells are then
60
+ truncated or wrapped until row relationships become unreadable. The source
61
+ Markdown remains unchanged in saved reports; only the terminal presentation
62
+ is adapted.
63
+ """
64
+ if width >= table_breakpoint or "|" not in (markup or ""):
65
+ return markup
66
+
67
+ lines = (markup or "").splitlines()
68
+ output: list[str] = []
69
+ index = 0
70
+ in_fence = False
71
+ while index < len(lines):
72
+ line = lines[index]
73
+ if line.lstrip().startswith("```"):
74
+ in_fence = not in_fence
75
+ output.append(line)
76
+ index += 1
77
+ continue
78
+
79
+ if (
80
+ not in_fence
81
+ and index + 1 < len(lines)
82
+ and line.strip().startswith("|")
83
+ and _is_markdown_table_separator(lines[index + 1])
84
+ ):
85
+ headers = _markdown_table_cells(line)
86
+ cursor = index + 2
87
+ rows: list[list[str]] = []
88
+ while cursor < len(lines) and lines[cursor].strip().startswith("|"):
89
+ row = _markdown_table_cells(lines[cursor])
90
+ if row:
91
+ rows.append(row)
92
+ cursor += 1
93
+
94
+ if headers and rows:
95
+ for row in rows:
96
+ pairs = [
97
+ (header, row[pos] if pos < len(row) else "—")
98
+ for pos, header in enumerate(headers)
99
+ if header
100
+ ]
101
+ if not pairs:
102
+ continue
103
+ first_header, first_value = pairs[0]
104
+ output.append(f"- **{first_header}**:{first_value or '—'}")
105
+ for header, value in pairs[1:]:
106
+ output.append(f" - **{header}**:{value or '—'}")
107
+ index = cursor
108
+ continue
109
+
110
+ output.append(line)
111
+ index += 1
112
+ return "\n".join(output)
113
+
114
+
115
+ # ── Tool display helpers ──────────────────────────────────────────────────────
116
+
117
+ def tool_display_kind(tool_name: str) -> str:
118
+ """Return a user-facing service/tool kind without exposing local targets."""
119
+ if tool_name.startswith("mcp__"):
120
+ return "MCP"
121
+ if tool_name in FINANCE_TOOL_NAMES:
122
+ return "finance tool"
123
+ if tool_name in {"web_search", "search_web"}:
124
+ return "web search"
125
+ if tool_name == "web_fetch":
126
+ return "web fetch"
127
+ if tool_name in {"read_file", "write_file", "edit_file", "list_files", "search_code"}:
128
+ return "file tool"
129
+ if tool_name == "run_command":
130
+ return "shell tool"
131
+ if tool_name.startswith("skill") or tool_name in {"TaskCreate", "TaskUpdate"}:
132
+ return "skill"
133
+ if tool_name.startswith("broker_") or tool_name in {"broker_query", "broker_order"}:
134
+ return "broker tool"
135
+ return "tool"
136
+
137
+
138
+ def tool_display_label(tool_name: str) -> str:
139
+ """Short label for activity UI: tool name plus its service kind."""
140
+ if tool_name.startswith("mcp__"):
141
+ parts = tool_name.split("__")
142
+ if len(parts) >= 3:
143
+ return f"{parts[1]} · {parts[2].replace('_', ' ')} · MCP"
144
+ return "MCP"
145
+ return f"{tool_name} · {tool_display_kind(tool_name)}"
146
+
147
+
148
+ def format_turn_footer(metadata, *, mode: str = "compact", copy_available: bool = False) -> str:
149
+ """Return the post-response status line.
150
+
151
+ ``full`` keeps the historical token-heavy line for debugging. ``compact``
152
+ is the default interactive UI: elapsed time, provider, tools, and /copy.
153
+ """
154
+ mode = (mode or "compact").strip().lower()
155
+ if mode in {"off", "none", "false", "0"}:
156
+ return ""
157
+ parts = list(getattr(metadata, "parts", []) or [])
158
+ if mode in {"full", "debug", "verbose"}:
159
+ footer = " · ".join(parts)
160
+ if copy_available:
161
+ footer = f"{footer} /copy" if footer else "/copy"
162
+ return footer
163
+
164
+ elapsed = parts[0] if parts else ""
165
+ provider = str(getattr(metadata, "provider", "") or "").strip()
166
+ tools = list(getattr(metadata, "tools", []) or [])
167
+ out: list[str] = []
168
+ if elapsed:
169
+ out.append(elapsed)
170
+ if provider and provider not in {"aws", "local"}:
171
+ out.append(provider)
172
+ if tools:
173
+ shown = " ".join(str(t) for t in tools[:3])
174
+ if len(tools) > 3:
175
+ shown += f" +{len(tools) - 3}"
176
+ out.append(shown)
177
+ if copy_available:
178
+ out.append("/copy")
179
+ return " · ".join(out)
180
+
181
+
182
+ def display_path(path: object, *, fallback: str = "file") -> str:
183
+ """Return a path-safe display value for user-facing UI."""
184
+ if not path:
185
+ return fallback
186
+ try:
187
+ name = pathlib.Path(str(path)).name
188
+ except Exception:
189
+ name = ""
190
+ return name or fallback
191
+
192
+
193
+ # ── Error helpers ──────────────────────────────────────────────────────────────
194
+
195
+ def clean_tool_error_message(error: object) -> str:
196
+ raw = str(error or "failed").strip()
197
+ low = raw.lower()
198
+ if not raw:
199
+ return "操作失败"
200
+ if "curl: (28)" in low or "timed out" in low or "timeout" in low:
201
+ return "请求超时,数据源暂时不可用。请稍后重试或运行 /health 检查服务。"
202
+ if "connection refused" in low:
203
+ return "连接被拒绝,服务暂时不可用。请检查本地服务或网络。"
204
+ if "connection aborted" in low or "remotedisconnected" in low:
205
+ return "网络连接中断,数据源未完成响应。请稍后重试。"
206
+ # Generic connection / proxy / DNS failures — collapse the verbose
207
+ # urllib3 HTTPSConnectionPool(...) dump into a single readable line.
208
+ if any(s in low for s in (
209
+ "httpsconnectionpool", "httpconnectionpool", "max retries exceeded",
210
+ "proxyerror", "failed to establish a new connection",
211
+ "nameresolutionerror", "getaddrinfo failed", "newconnectionerror",
212
+ )):
213
+ import re as _re3
214
+ _host = _re3.search(r"host=['\"]([^'\"]+)['\"]", raw)
215
+ _hint = f"(数据源 {_host.group(1)})" if _host else ""
216
+ return f"数据源连接失败{_hint},可能是网络或代理问题。请检查网络后重试。"
217
+ if "rate" in low or "429" in low or "too many requests" in low:
218
+ return "数据源请求频率受限,请稍后重试。"
219
+ # Collapse verbose HTTP error strings: "web_fetch failed: 401 Client Error: Unauthorized for url: https://..."
220
+ import re as _re
221
+ _http = _re.match(r"web_fetch failed:\s*(\d{3})\s+\w[\w\s]+?:\s*([\w\s]+?)(?:\s+for url:.*)?$", raw, _re.I)
222
+ if _http:
223
+ code, phrase = _http.group(1), _http.group(2).strip()
224
+ return f"HTTP {code} {phrase}"
225
+ if "traceback" in low:
226
+ return raw.splitlines()[-1][:160] if raw.splitlines() else "运行失败"
227
+ return raw[:200]
228
+
229
+
230
+ def error_hint(error: str, context: str = "") -> str:
231
+ err_lower = error.lower() if error else ""
232
+ if "connection" in err_lower or "refused" in err_lower or "unreachable" in err_lower:
233
+ return "Hint: Backend unreachable. Try /health or check your network."
234
+ if "timeout" in err_lower or "timed out" in err_lower:
235
+ return "Hint: Request timed out. Try again or check /health."
236
+ # External web pages that block scraping (paywall / anti-bot) — NOT an Aria
237
+ # login problem, so /login must not be suggested.
238
+ _is_web = any(m in err_lower for m in (
239
+ "http://", "https://", "www.", ".com", ".org", ".net",
240
+ "web_fetch", "web fetch", "forbidden",
241
+ ))
242
+ if "401" in err_lower or "unauthorized" in err_lower:
243
+ if any(h in err_lower for h in ("finnhub", "alphavantage", "polygon", "api/v1", "api/v2/finance")):
244
+ return "Hint: API key required — /apikey set finnhub <KEY> (free at finnhub.io)"
245
+ if _is_web:
246
+ return "Hint: This site blocks automated access (paywall/anti-bot). Try another source."
247
+ return "Hint: Authentication required. Run /login to sign in."
248
+ if "403" in err_lower or "forbidden" in err_lower:
249
+ if _is_web:
250
+ return "Hint: This site blocks automated access (paywall/anti-bot). Try another source."
251
+ return "Hint: Access denied. Check your API key or subscription."
252
+ if "429" in err_lower or "rate" in err_lower:
253
+ return "Hint: Rate limited. Wait a moment and try again."
254
+ if ("ollama" in err_lower or "ollama http" in err_lower) and (
255
+ "not found" in err_lower or "404" in err_lower
256
+ ):
257
+ m = re.search(r"model ['\"]?([^'\"]+)['\"]? not found", err_lower)
258
+ model_hint = m.group(1) if m else "the requested model"
259
+ try:
260
+ from local_llm_provider import list_ollama_models
261
+ available = list_ollama_models("http://localhost:11434")
262
+ if available:
263
+ suggestion = available[0]
264
+ return (
265
+ f"Hint: Ollama model '{model_hint}' not found.\n"
266
+ f" Available: {', '.join(available[:4])}\n"
267
+ f" Run: /config model {suggestion}"
268
+ )
269
+ except Exception:
270
+ pass
271
+ return (
272
+ "Hint: Ollama model not found. Run `ollama list` to see available models.\n"
273
+ " Or pull one: ollama pull qwen2.5-coder:7b"
274
+ )
275
+ # "File not found" is a path error. Tell the model firmly NOT to keep
276
+ # guessing filenames (it otherwise loops app.py→script.py→main.py…).
277
+ if "file not found" in err_lower or "no such file" in err_lower:
278
+ return ("Hint: This file does not exist. Do NOT guess other filenames — "
279
+ "list the directory first, or this question may not need a file at all.")
280
+ if "404" in err_lower and context == "tool":
281
+ return "Hint: Tool not available. Check /tools for available tools."
282
+ if "not found" in err_lower and context == "session":
283
+ return "Hint: Session not found. Run /sessions to list available."
284
+ if "404" in err_lower or ("not found" in err_lower and context not in ("tool", "")):
285
+ return "Hint: Resource not found. Check the symbol or path."
286
+ if "no data" in err_lower or "no result" in err_lower:
287
+ return "Hint: No data returned. Verify the symbol spelling."
288
+ if "500" in err_lower or "internal" in err_lower:
289
+ return "Hint: Server error. Try again in a moment or /health to check."
290
+ if context == "login":
291
+ return "Hint: Check email/password. Usage: /login email password"
292
+ return ""
293
+
294
+
295
+ # ── Error panel & tree formatting ──────────────────────────────────────────────
296
+
297
+ def print_error(
298
+ msg: str,
299
+ context: str = "",
300
+ *,
301
+ console,
302
+ has_rich: bool,
303
+ rich_box=None,
304
+ use_panel: bool = False,
305
+ ) -> None:
306
+ hint = error_hint(msg, context)
307
+ if not use_panel:
308
+ # Claude Code style: clean └ tree connector for inline error guidance
309
+ if has_rich:
310
+ console.print(f" [red]└ {msg}[/red]")
311
+ if hint:
312
+ for hline in hint.splitlines():
313
+ console.print(f" [dim]{hline}[/dim]")
314
+ else:
315
+ print(f" └ {msg}")
316
+ if hint:
317
+ for hline in hint.splitlines():
318
+ print(f" {hline}")
319
+ return
320
+
321
+ if has_rich:
322
+ from rich.panel import Panel
323
+ box_style = getattr(rich_box, "ROUNDED", None) if rich_box else None
324
+ body = f"[red]{msg}[/red]"
325
+ if hint:
326
+ body += f"\n[dim]{hint}[/dim]"
327
+ console.print(Panel(body, border_style="red", box=box_style, padding=(0, 1)))
328
+ else:
329
+ print(msg)
330
+
331
+
332
+ # ── Tool result ────────────────────────────────────────────────────────────────
333
+
334
+ def print_tool_result(
335
+ tool_name: str,
336
+ result: dict,
337
+ elapsed: float = 0,
338
+ params: dict = None,
339
+ *,
340
+ console,
341
+ has_rich: bool,
342
+ rich_box,
343
+ print_finance_fn, # callable(tool_name, result) for finance tools
344
+ bot_mode: bool = False,
345
+ ) -> None:
346
+ """Render a tool result summary — Codex-style ⎿ tree connector."""
347
+ if bot_mode:
348
+ return
349
+
350
+ ts = f" [dim]{elapsed:.1f}s[/dim]" if elapsed >= 0.1 else ""
351
+ ts_plain = f" {elapsed:.1f}s" if elapsed >= 0.1 else ""
352
+ params = params or {}
353
+
354
+ if tool_name in FINANCE_TOOL_NAMES:
355
+ print_finance_fn(tool_name, result)
356
+ if ts and has_rich:
357
+ console.print(f" [dim]⎿[/dim]{ts}")
358
+ return
359
+
360
+ if result.get("success"):
361
+ data = result.get("data", {})
362
+
363
+ if tool_name == "write_file":
364
+ lines = data.get("lines") or (params.get("content", "").count("\n") + 1 if params.get("content") else 0)
365
+ size = data.get("size_bytes") or len((params.get("content", "") or "").encode())
366
+ size_str = f"{size}B" if size < 1024 else f"{size // 1024}KB"
367
+ if has_rich:
368
+ console.print(f" [dim]⎿[/dim] [green]✓[/green] [dim]file tool {lines} lines {size_str}[/dim]{ts}")
369
+ else:
370
+ print(f" ⎿ ✓ file tool {lines} lines {size_str}{ts_plain}")
371
+
372
+ elif tool_name == "edit_file":
373
+ old = params.get("old_string", "")
374
+ new = params.get("new_string", "")
375
+ if old and new and has_rich:
376
+ diff = list(difflib.unified_diff(
377
+ old.splitlines(keepends=True),
378
+ new.splitlines(keepends=True),
379
+ lineterm="",
380
+ ))
381
+ if diff:
382
+ _hdr = " [dim]⎿[/dim] [#C08050]file tool[/#C08050]"
383
+ console.print(f"{_hdr}{ts}")
384
+ from rich.syntax import Syntax
385
+ from rich.panel import Panel
386
+ diff_text = "".join(diff[2:])
387
+ console.print(Panel(
388
+ Syntax(diff_text, "diff", theme="monokai", padding=(0, 1)),
389
+ border_style="dim",
390
+ box=rich_box.SIMPLE,
391
+ ))
392
+ else:
393
+ console.print(f" [dim]⎿ no change[/dim]{ts}")
394
+ elif has_rich:
395
+ console.print(f" [dim]⎿ edited[/dim]{ts}")
396
+ else:
397
+ print(f" ⎿ edited{ts_plain}")
398
+
399
+ elif tool_name == "run_command":
400
+ stdout = data.get("stdout", "").strip()
401
+ returncode = data.get("returncode", data.get("exit_code", 0))
402
+ if has_rich:
403
+ from rich.panel import Panel
404
+ rc_color = "green" if returncode == 0 else "red"
405
+ rc_icon = "✓" if returncode == 0 else "✗"
406
+ console.print(f" [dim]⎿[/dim] [{rc_color}]{rc_icon} exit {returncode}[/{rc_color}]{ts}")
407
+ if stdout:
408
+ out_lines = stdout.splitlines()
409
+ if len(out_lines) > 15:
410
+ head = out_lines[:5]
411
+ tail = out_lines[-10:]
412
+ truncated = "\n".join(head) + f"\n[dim]… ({len(out_lines) - 15} lines hidden) …[/dim]\n" + "\n".join(tail)
413
+ if data.get("full_output_path"):
414
+ truncated += f"\n[dim]full output saved: {data.get('full_output_path')}[/dim]"
415
+ console.print(Panel(
416
+ f"[dim]{truncated}[/dim]",
417
+ border_style="dim",
418
+ box=rich_box.SIMPLE,
419
+ padding=(0, 1),
420
+ ))
421
+ else:
422
+ truncated = "\n".join(out_lines)
423
+ if data.get("full_output_path"):
424
+ truncated += f"\n[dim]full output saved: {data.get('full_output_path')}[/dim]"
425
+ console.print(Panel(
426
+ f"[dim]{truncated}[/dim]",
427
+ border_style="dim",
428
+ box=rich_box.SIMPLE,
429
+ padding=(0, 1),
430
+ ))
431
+ else:
432
+ print(f" ⎿ exit {returncode}{ts_plain}")
433
+ for ol in stdout.splitlines()[:4]:
434
+ print(f" {ol[:100]}")
435
+
436
+ elif tool_name == "read_file":
437
+ lines = data.get("lines", 0)
438
+ if has_rich:
439
+ console.print(f" [dim]⎿ file tool {lines} lines[/dim]{ts}")
440
+ else:
441
+ print(f" ⎿ file tool {lines} lines{ts_plain}")
442
+
443
+ elif tool_name == "list_files":
444
+ count = data.get("count", 0)
445
+ if has_rich:
446
+ color = "yellow" if count == 0 else "dim"
447
+ msg = "0 items — no matches" if count == 0 else f"{count} items"
448
+ console.print(f" [{color}]⎿ {msg}[/{color}]{ts}")
449
+ else:
450
+ print(f" ⎿ {count} items{ts_plain}")
451
+
452
+ elif tool_name == "search_code":
453
+ matches = len(data.get("matches", []))
454
+ if has_rich:
455
+ console.print(f" [dim]⎿ {matches} matches[/dim]{ts}")
456
+ else:
457
+ print(f" ⎿ {matches} matches{ts_plain}")
458
+
459
+ elif tool_name == "web_fetch":
460
+ length = data.get("length", 0)
461
+ trunc = data.get("truncated", False)
462
+ len_str = f" {length:,} chars" if length else ""
463
+ trunc_str = " [dim]truncated[/dim]" if trunc else ""
464
+ if has_rich:
465
+ console.print(f" [dim]⎿ web fetch{len_str}[/dim]{trunc_str}{ts}")
466
+ else:
467
+ print(f" ⎿ web fetch{ts_plain}")
468
+
469
+ elif tool_name in ("web_search", "search_web"):
470
+ results = data.get("results", [])
471
+ count = len(results)
472
+ if has_rich:
473
+ console.print(f" [dim]⎿ {count} results[/dim]{ts}")
474
+ else:
475
+ print(f" ⎿ {count} results{ts_plain}")
476
+
477
+ else:
478
+ short = tool_display_kind(tool_name)
479
+ if has_rich:
480
+ console.print(f" [dim]⎿ {short} done[/dim]{ts}")
481
+ else:
482
+ print(f" ⎿ done{ts_plain}")
483
+
484
+ else:
485
+ error = clean_tool_error_message(result.get("error", "failed"))
486
+ hint = error_hint(str(error), context="tool")
487
+ if has_rich:
488
+ console.print(f" [dim]⎿[/dim] [red]✗ {error[:120]}[/red]")
489
+ if hint:
490
+ console.print(f" [dim]{hint}[/dim]")
491
+ else:
492
+ print(f" ⎿ ✗ {error[:80]}")
493
+
494
+
495
+ # ── Activity group (OpenClaw-style batch summary) ──────────────────────────────
496
+
497
+ def _one_line_tool_summary(
498
+ tool_name: str,
499
+ result: dict,
500
+ elapsed: float,
501
+ params: dict,
502
+ ) -> tuple[str, str]:
503
+ """Return (status_markup, detail_markup) for one tool in an activity table."""
504
+ params = params or {}
505
+ ts = f"[dim] {elapsed:.1f}s[/dim]" if elapsed >= 0.1 else ""
506
+
507
+ if not result.get("success"):
508
+ error = clean_tool_error_message(result.get("error", "failed"))
509
+ return "[red]✗[/red]", f"[red]{error[:80]}[/red]{ts}"
510
+
511
+ data = result.get("data", {})
512
+ kind = tool_display_kind(tool_name)
513
+
514
+ if tool_name == "write_file":
515
+ lines = data.get("lines") or (params.get("content", "").count("\n") + 1 if params.get("content") else 0)
516
+ size = data.get("size_bytes") or len((params.get("content", "") or "").encode())
517
+ size_str = f"{size}B" if size < 1024 else f"{size // 1024}KB"
518
+ return "[green]✓[/green]", f"[dim]{kind} {lines} lines {size_str}[/dim]{ts}"
519
+
520
+ elif tool_name == "edit_file":
521
+ return "[green]✓[/green]", f"[dim]edited {kind}[/dim]{ts}"
522
+
523
+ elif tool_name == "run_command":
524
+ rc = data.get("returncode", data.get("exit_code", 0))
525
+ icon = "[green]✓[/green]" if rc == 0 else "[red]✗[/red]"
526
+ color = "green" if rc == 0 else "red"
527
+ suffix = " [dim]· full output saved[/dim]" if data.get("full_output_path") else ""
528
+ return icon, f"[{color}]exit {rc}[/{color}]{suffix}{ts}"
529
+
530
+ elif tool_name == "read_file":
531
+ lines = data.get("lines", 0)
532
+ return "[green]✓[/green]", f"[dim]{kind} {lines} lines[/dim]{ts}"
533
+
534
+ elif tool_name == "list_files":
535
+ count = data.get("count", 0)
536
+ color = "yellow" if count == 0 else "dim"
537
+ msg = "no matches" if count == 0 else f"{count} items"
538
+ return "[green]✓[/green]", f"[{color}]{msg}[/{color}]{ts}"
539
+
540
+ elif tool_name == "search_code":
541
+ matches = len(data.get("matches", []))
542
+ return "[green]✓[/green]", f"[dim]{matches} matches[/dim]{ts}"
543
+
544
+ elif tool_name == "web_fetch":
545
+ length = data.get("length", 0)
546
+ len_s = f" {length:,}c" if length else ""
547
+ return "[green]✓[/green]", f"[dim]{kind}{len_s}[/dim]{ts}"
548
+
549
+ elif tool_name in ("web_search", "search_web"):
550
+ count = len(data.get("results", []))
551
+ return "[green]✓[/green]", f"[dim]{count} results[/dim]{ts}"
552
+
553
+ else:
554
+ return "[green]✓[/green]", f"[dim]{kind} done[/dim]{ts}"
555
+
556
+
557
+ def print_tool_activity_group(
558
+ results: list, # list of (tool_name, result, elapsed, params)
559
+ *,
560
+ console,
561
+ has_rich: bool,
562
+ rich_box,
563
+ print_finance_fn,
564
+ bot_mode: bool = False,
565
+ ) -> None:
566
+ """Render multiple tool results as a compact Activity block (OpenClaw style).
567
+
568
+ For N >= 2 tools: prints a titled table.
569
+ For N == 1: delegates to print_tool_result (single-line).
570
+ """
571
+ if bot_mode or not results:
572
+ return
573
+
574
+ if len(results) == 1:
575
+ tool_name, result, elapsed, params = results[0]
576
+ print_tool_result(tool_name, result, elapsed, params,
577
+ console=console, has_rich=has_rich, rich_box=rich_box,
578
+ print_finance_fn=print_finance_fn, bot_mode=bot_mode)
579
+ return
580
+
581
+ total_elapsed = sum(e for _, _, e, _ in results)
582
+ n = len(results)
583
+
584
+ # Finance tools: print with dedicated renderer, then add to activity table
585
+ finance_rows = []
586
+ for tool_name, result, elapsed, params in results:
587
+ if tool_name in FINANCE_TOOL_NAMES:
588
+ print_finance_fn(tool_name, result)
589
+ finance_rows.append(tool_name)
590
+
591
+ if has_rich:
592
+ from rich.table import Table
593
+ ts_total = f" [dim]{total_elapsed:.1f}s[/dim]" if total_elapsed >= 0.1 else ""
594
+ header = f"[dim]Activity · {n} tools[/dim]{ts_total}"
595
+ console.print(f"\n {header}")
596
+ tbl = Table.grid(padding=(0, 2))
597
+ tbl.add_column(no_wrap=True, min_width=14, style="dim") # tool name
598
+ tbl.add_column(no_wrap=True, min_width=2) # status icon
599
+ tbl.add_column() # detail
600
+
601
+ from collections import OrderedDict
602
+ _mcp_groups: "OrderedDict[str, list]" = OrderedDict()
603
+ for tool_name, result, elapsed, params in results:
604
+ if tool_name in finance_rows:
605
+ icon = "[green]✓[/green]" if result.get("success") else "[red]✗[/red]"
606
+ tbl.add_row(tool_name, icon, "")
607
+ elif tool_name.startswith("mcp__"):
608
+ # Defer MCP calls — collapse per server below
609
+ _server = tool_name.split("__")[1] if len(tool_name.split("__")) >= 2 else "mcp"
610
+ _mcp_groups.setdefault(_server, []).append((tool_name, result))
611
+ else:
612
+ icon, detail = _one_line_tool_summary(tool_name, result, elapsed, params)
613
+ tbl.add_row(f"[dim]{tool_name}[/dim]", icon, detail)
614
+
615
+ # Collapsed MCP rows: "server · tool" for one, "called N times" for many
616
+ for _server, _calls in _mcp_groups.items():
617
+ _all_ok = all(r.get("success") for _, r in _calls)
618
+ _icon = "[green]✓[/green]" if _all_ok else "[red]✗[/red]"
619
+ if len(_calls) == 1:
620
+ _tn = _calls[0][0].split("__")
621
+ _label = _tn[2].replace("_", " ") if len(_tn) >= 3 else _server
622
+ tbl.add_row(f"[dim]{_server}[/dim]", _icon, f"[dim]{_label} · MCP[/dim]")
623
+ else:
624
+ tbl.add_row(f"[dim]{_server}[/dim]", _icon,
625
+ f"[dim]called {len(_calls)} times · MCP[/dim]")
626
+
627
+ from rich.padding import Padding
628
+ console.print(Padding(tbl, (0, 0, 0, 4)))
629
+
630
+ # For run_command with stdout, still print the output panel
631
+ for tool_name, result, elapsed, params in results:
632
+ if tool_name == "run_command" and result.get("success"):
633
+ stdout = result.get("data", {}).get("stdout", "").strip()
634
+ if stdout:
635
+ from rich.panel import Panel
636
+ out_lines = stdout.splitlines()
637
+ if len(out_lines) > 15:
638
+ head = out_lines[:5]
639
+ tail = out_lines[-10:]
640
+ truncated = "\n".join(head) + f"\n[dim]… ({len(out_lines) - 15} lines hidden) …[/dim]\n" + "\n".join(tail)
641
+ if result.get("data", {}).get("full_output_path"):
642
+ truncated += f"\n[dim]full output saved: {result.get('data', {}).get('full_output_path')}[/dim]"
643
+ console.print(Panel(f"[dim]{truncated}[/dim]",
644
+ border_style="dim", box=rich_box.SIMPLE,
645
+ padding=(0, 1)))
646
+ else:
647
+ truncated = "\n".join(out_lines)
648
+ if result.get("data", {}).get("full_output_path"):
649
+ truncated += f"\n[dim]full output saved: {result.get('data', {}).get('full_output_path')}[/dim]"
650
+ console.print(Panel(f"[dim]{truncated}[/dim]",
651
+ border_style="dim", box=rich_box.SIMPLE,
652
+ padding=(0, 1)))
653
+
654
+ # For edit_file, still print diff
655
+ elif tool_name == "edit_file" and result.get("success"):
656
+ old = (params or {}).get("old_string", "")
657
+ new = (params or {}).get("new_string", "")
658
+ if old and new:
659
+ diff = list(difflib.unified_diff(
660
+ old.splitlines(keepends=True),
661
+ new.splitlines(keepends=True),
662
+ lineterm="",
663
+ ))
664
+ if diff:
665
+ from rich.syntax import Syntax
666
+ from rich.padding import Padding
667
+ diff_text = "".join(diff[2:])
668
+ console.print(Padding(Panel(
669
+ Syntax(diff_text, "diff", theme="monokai", padding=(0, 1)),
670
+ border_style="dim", box=rich_box.SIMPLE
671
+ ), (0, 0, 0, 4)))
672
+ else:
673
+ ts_total = f" {total_elapsed:.1f}s" if total_elapsed >= 0.1 else ""
674
+ print(f"\n Activity · {n} tools{ts_total}")
675
+ for tool_name, result, elapsed, params in results:
676
+ icon, detail = _one_line_tool_summary(tool_name, result, elapsed, params)
677
+ detail_plain = re.sub(r"\[/?[^\]]+\]", "", detail)
678
+ icon_plain = "✓" if result.get("success") else "✗"
679
+ print(f" {tool_name:<18}{icon_plain} {detail_plain}")
680
+
681
+
682
+ # ── Fallback / model-switch toast ──────────────────────────────────────────────
683
+
684
+ def print_fallback_toast(
685
+ from_provider: str,
686
+ to_provider: str,
687
+ reason: str = "",
688
+ *,
689
+ console,
690
+ has_rich: bool,
691
+ ) -> None:
692
+ """Show a transient yellow notification when the active model/provider switches."""
693
+ if not has_rich:
694
+ print(f"\n ⚡ 模型切换 {from_provider} → {to_provider}{(' ' + reason) if reason else ''}")
695
+ return
696
+ body = f"[bold #C08050]⚡[/bold #C08050] [#C08050]{from_provider}[/#C08050] [dim]→[/dim] [#C08050]{to_provider}[/#C08050]"
697
+ if reason:
698
+ body += f"\n [dim]{reason}[/dim]"
699
+ console.print(f"\n {body}")
700
+
701
+
702
+ # ── Context pressure warning ───────────────────────────────────────────────────
703
+
704
+ _CTX_WARNED: dict[str, float] = {} # session_id → last warn time
705
+
706
+ def print_context_warning(
707
+ est_tokens: int,
708
+ max_tokens: int,
709
+ *,
710
+ console,
711
+ has_rich: bool,
712
+ session_id: str = "",
713
+ cooldown: float = 120.0, # only warn once every 2 min per session
714
+ ) -> None:
715
+ """Warn when context is >85% full; rate-limited to avoid spam."""
716
+ if max_tokens <= 0:
717
+ return
718
+ ratio = est_tokens / max_tokens
719
+ if ratio < 0.85:
720
+ return
721
+ now = time.monotonic()
722
+ if now - _CTX_WARNED.get(session_id, 0) < cooldown:
723
+ return
724
+ _CTX_WARNED[session_id] = now
725
+
726
+ def _k(n: int) -> str:
727
+ return f"{n // 1000}K" if n >= 1000 else str(n)
728
+
729
+ pct = int(ratio * 100)
730
+ if has_rich:
731
+ color = "red" if ratio >= 0.95 else "#C08050"
732
+ icon = "●" if ratio >= 0.95 else "⚠"
733
+ msg = f" [{color}]{icon} 上下文 {pct}% 已满 ({_k(est_tokens)}/{_k(max_tokens)} tokens)[/{color}]"
734
+ msg += " [dim]→ /compact 压缩历史 /clear 重置[/dim]"
735
+ console.print(msg)
736
+ else:
737
+ print(f" ⚠ 上下文 {pct}% ({_k(est_tokens)}/{_k(max_tokens)} tokens) — /compact 或 /clear")
738
+
739
+
740
+ # ── Blocked / cancelled tool visual ───────────────────────────────────────────
741
+
742
+ def print_tool_blocked(
743
+ tool_name: str,
744
+ reason: str = "用户取消",
745
+ *,
746
+ console,
747
+ has_rich: bool,
748
+ ) -> None:
749
+ """Show a styled 'Blocked' line when tool execution is denied or cancelled."""
750
+ if has_rich:
751
+ console.print(
752
+ f" [dim]⎿[/dim] [#C08050]⊘ {tool_name}[/#C08050] [dim]{reason}[/dim]"
753
+ )
754
+ else:
755
+ print(f" ⎿ ⊘ {tool_name} {reason}")
756
+
757
+
758
+ # ── Robot thinking / response header ──────────────────────────────────────────
759
+
760
+ def print_thinking_header(*, console, has_rich: bool, agent_name: str = "Aria", color: str = "#C08050") -> None:
761
+ """Print a subtle copper 'Aria ▸' header before each AI response stream.
762
+
763
+ Gives the response a clear starting-point rather than appearing inline.
764
+ Called once per turn, right before the first streaming token is printed.
765
+ """
766
+ if not has_rich:
767
+ return
768
+ console.print(f"[bold {color}]▣[/bold {color}] [dim {color}]{agent_name}[/dim {color}]", end=" ")
769
+
770
+
771
+ def print_done_footer(elapsed: float, *, console, has_rich: bool) -> None:
772
+ """Print a dim elapsed-time line after the response stream ends."""
773
+ if not has_rich:
774
+ return
775
+ console.print(f"\n[dim] ✓ {elapsed:.1f}s[/dim]")