aria-code 0.167.0__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (601) hide show
  1. aria_bots/__init__.py +7 -0
  2. aria_bots/channels/README.md +97 -0
  3. aria_bots/channels/__init__.py +1 -0
  4. aria_bots/channels/approvals.py +263 -0
  5. aria_bots/channels/conversation.py +280 -0
  6. aria_bots/channels/digest.py +186 -0
  7. aria_bots/channels/registry.py +95 -0
  8. aria_bots/engine.py +144 -0
  9. aria_bots/feishu_bot.py +1385 -0
  10. aria_bots/logistics/__init__.py +1 -0
  11. aria_bots/logistics/carriers.py +253 -0
  12. aria_bots/logistics/freight.py +154 -0
  13. aria_bots/logistics/inventory.py +242 -0
  14. aria_bots/logistics/mcp_server.py +156 -0
  15. aria_bots/logistics/records.py +119 -0
  16. aria_bots/logistics/tenancy.py +152 -0
  17. aria_bots/market.py +91 -0
  18. aria_bots/relay_client.py +326 -0
  19. aria_bots/telegram_bot.py +299 -0
  20. aria_code/__init__.py +111 -0
  21. aria_code/_bin/aria-native.exe +0 -0
  22. aria_code/_bin/aria-runtime.exe +0 -0
  23. aria_code/_version.py +20 -0
  24. aria_code/account_memory.py +296 -0
  25. aria_code/account_session.py +166 -0
  26. aria_code/adk_apps/__init__.py +1 -0
  27. aria_code/adk_apps/aria_code_review/__init__.py +1 -0
  28. aria_code/adk_apps/aria_code_review/agent.py +32 -0
  29. aria_code/adk_apps/aria_research/__init__.py +1 -0
  30. aria_code/adk_apps/aria_research/agent.py +31 -0
  31. aria_code/agents/__init__.py +49 -0
  32. aria_code/agents/base.py +329 -0
  33. aria_code/agents/code_review.py +200 -0
  34. aria_code/agents/deep/__init__.py +37 -0
  35. aria_code/agents/deep/calibration_loop.py +144 -0
  36. aria_code/agents/deep/critic.py +125 -0
  37. aria_code/agents/deep/deepen.py +193 -0
  38. aria_code/agents/deep/models.py +149 -0
  39. aria_code/agents/deep/pipeline.py +164 -0
  40. aria_code/agents/deep/quant_fusion.py +192 -0
  41. aria_code/agents/deep/themes.py +95 -0
  42. aria_code/agents/deep/tiers.py +106 -0
  43. aria_code/agents/engineering/__init__.py +14 -0
  44. aria_code/agents/engineering/coder.py +222 -0
  45. aria_code/agents/engineering/tester.py +145 -0
  46. aria_code/agents/financial/__init__.py +21 -0
  47. aria_code/agents/financial/cashflow_burn.py +119 -0
  48. aria_code/agents/financial/catalyst.py +279 -0
  49. aria_code/agents/financial/corporate_finance.py +175 -0
  50. aria_code/agents/financial/debate.py +224 -0
  51. aria_code/agents/financial/earnings.py +315 -0
  52. aria_code/agents/financial/fundamental.py +178 -0
  53. aria_code/agents/financial/macro.py +111 -0
  54. aria_code/agents/financial/news.py +231 -0
  55. aria_code/agents/financial/risk.py +146 -0
  56. aria_code/agents/financial/sector.py +279 -0
  57. aria_code/agents/financial/strategist.py +121 -0
  58. aria_code/agents/financial/stripe_revenue.py +127 -0
  59. aria_code/agents/financial/synthesis.py +274 -0
  60. aria_code/agents/financial/technical.py +277 -0
  61. aria_code/agents/orchestrator.py +44 -0
  62. aria_code/agents/portfolio_agent.py +392 -0
  63. aria_code/agents/realty/__init__.py +62 -0
  64. aria_code/agents/realty/asset_diagnosis.py +152 -0
  65. aria_code/agents/realty/business_match.py +166 -0
  66. aria_code/agents/realty/cashflow_verify.py +209 -0
  67. aria_code/agents/realty/contract_rules.py +210 -0
  68. aria_code/agents/realty/energy_anomaly.py +189 -0
  69. aria_code/agents/realty/exit_settlement.py +208 -0
  70. aria_code/agents/realty/fulfillment_risk.py +206 -0
  71. aria_code/agents/realty/ops_optimize.py +160 -0
  72. aria_code/agents/realty/revenue_share.py +215 -0
  73. aria_code/agents/registry.py +168 -0
  74. aria_code/agents/signal_scheme.py +111 -0
  75. aria_code/agents/sports/__init__.py +0 -0
  76. aria_code/agents/sports/football_agent.py +197 -0
  77. aria_code/agents/supervisor.py +113 -0
  78. aria_code/agents/team.py +500 -0
  79. aria_code/agents/video/__init__.py +16 -0
  80. aria_code/agents/video/director.py +376 -0
  81. aria_code/agents/video/qa_agent.py +143 -0
  82. aria_code/agents/video/script_doctor.py +112 -0
  83. aria_code/agents/warehouse/__init__.py +25 -0
  84. aria_code/agents/warehouse/contracts.py +26 -0
  85. aria_code/agents/warehouse/dashboard.py +155 -0
  86. aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
  87. aria_code/agents/warehouse/inbound_exceptions.py +25 -0
  88. aria_code/agents/warehouse/inventory_health.py +24 -0
  89. aria_code/agents/warehouse/local_run.py +71 -0
  90. aria_code/agents/warehouse/local_snapshot.py +132 -0
  91. aria_code/agents/warehouse/logistics_cost.py +84 -0
  92. aria_code/agents/warehouse/logistics_sync.py +21 -0
  93. aria_code/agents/warehouse/navigation.schema.json +110 -0
  94. aria_code/agents/warehouse/task_agent.py +172 -0
  95. aria_code/agents/warehouse/workflow.py +31 -0
  96. aria_code/aliyun_data_client.py +4 -0
  97. aria_code/apps/README.md +12 -0
  98. aria_code/apps/__init__.py +2 -0
  99. aria_code/apps/channels/README.md +9 -0
  100. aria_code/apps/channels/__init__.py +17 -0
  101. aria_code/apps/channels/approvals.py +11 -0
  102. aria_code/apps/channels/conversation.py +11 -0
  103. aria_code/apps/channels/digest.py +11 -0
  104. aria_code/apps/channels/intake.py +92 -0
  105. aria_code/apps/channels/registry.py +11 -0
  106. aria_code/apps/channels/tradingview.py +94 -0
  107. aria_code/apps/cli/README.md +13 -0
  108. aria_code/apps/cli/__init__.py +2 -0
  109. aria_code/apps/cli/_optional.py +120 -0
  110. aria_code/apps/cli/app_server.py +656 -0
  111. aria_code/apps/cli/auto_update.py +125 -0
  112. aria_code/apps/cli/bootstrap.py +206 -0
  113. aria_code/apps/cli/broker_render.py +206 -0
  114. aria_code/apps/cli/chat_turn.py +1327 -0
  115. aria_code/apps/cli/cloud_models.py +231 -0
  116. aria_code/apps/cli/codegen_paths.py +29 -0
  117. aria_code/apps/cli/commands/__init__.py +34 -0
  118. aria_code/apps/cli/commands/_business_render.py +78 -0
  119. aria_code/apps/cli/commands/_ui.py +71 -0
  120. aria_code/apps/cli/commands/analysis_cmds.py +378 -0
  121. aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
  122. aria_code/apps/cli/commands/auth_cmds.py +215 -0
  123. aria_code/apps/cli/commands/backtest_cmds.py +2518 -0
  124. aria_code/apps/cli/commands/broker_cmds.py +1236 -0
  125. aria_code/apps/cli/commands/business_workflow_cmds.py +379 -0
  126. aria_code/apps/cli/commands/canvas_cmds.py +77 -0
  127. aria_code/apps/cli/commands/catalog.py +189 -0
  128. aria_code/apps/cli/commands/core_cmds.py +2748 -0
  129. aria_code/apps/cli/commands/data_cmds.py +521 -0
  130. aria_code/apps/cli/commands/diagnostic_cmds.py +285 -0
  131. aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
  132. aria_code/apps/cli/commands/engineering_cmds.py +399 -0
  133. aria_code/apps/cli/commands/file_cmds.py +312 -0
  134. aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
  135. aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
  136. aria_code/apps/cli/commands/market.py +544 -0
  137. aria_code/apps/cli/commands/market_cmds.py +1419 -0
  138. aria_code/apps/cli/commands/market_context.py +432 -0
  139. aria_code/apps/cli/commands/market_render.py +7 -0
  140. aria_code/apps/cli/commands/model_cmds.py +2374 -0
  141. aria_code/apps/cli/commands/ops_cmds.py +721 -0
  142. aria_code/apps/cli/commands/orchestrator_cmds.py +181 -0
  143. aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
  144. aria_code/apps/cli/commands/portfolio_cmds.py +1316 -0
  145. aria_code/apps/cli/commands/report.py +476 -0
  146. aria_code/apps/cli/commands/scaffold_templates.py +617 -0
  147. aria_code/apps/cli/commands/session_cmds.py +236 -0
  148. aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
  149. aria_code/apps/cli/commands/team.py +723 -0
  150. aria_code/apps/cli/commands/team_render.py +8 -0
  151. aria_code/apps/cli/commands/ui_cmds.py +401 -0
  152. aria_code/apps/cli/commands/warehouse_cmds.py +297 -0
  153. aria_code/apps/cli/commands/workflow_cmds.py +629 -0
  154. aria_code/apps/cli/commands/workspace_cmds.py +1543 -0
  155. aria_code/apps/cli/config_paths.py +73 -0
  156. aria_code/apps/cli/config_store.py +158 -0
  157. aria_code/apps/cli/constants.py +88 -0
  158. aria_code/apps/cli/context.py +34 -0
  159. aria_code/apps/cli/conversation_policy.py +53 -0
  160. aria_code/apps/cli/deterministic.py +200 -0
  161. aria_code/apps/cli/direct.py +48 -0
  162. aria_code/apps/cli/earnings_view.py +172 -0
  163. aria_code/apps/cli/endpoint_migration.py +64 -0
  164. aria_code/apps/cli/exec_events.py +255 -0
  165. aria_code/apps/cli/file_references.py +155 -0
  166. aria_code/apps/cli/finance_service_catalog.py +111 -0
  167. aria_code/apps/cli/football_reports.py +231 -0
  168. aria_code/apps/cli/github_app_auth.py +135 -0
  169. aria_code/apps/cli/google_login.py +239 -0
  170. aria_code/apps/cli/handlers/__init__.py +11 -0
  171. aria_code/apps/cli/handlers/broker_handlers.py +122 -0
  172. aria_code/apps/cli/handlers/chart_handlers.py +1314 -0
  173. aria_code/apps/cli/handlers/market_handlers.py +2570 -0
  174. aria_code/apps/cli/handlers/realty_handlers.py +114 -0
  175. aria_code/apps/cli/handlers/sports_handlers.py +22 -0
  176. aria_code/apps/cli/handlers/strategy_advice.py +82 -0
  177. aria_code/apps/cli/headless.py +352 -0
  178. aria_code/apps/cli/helpers.py +326 -0
  179. aria_code/apps/cli/hooks.py +180 -0
  180. aria_code/apps/cli/i18n.py +310 -0
  181. aria_code/apps/cli/intent.py +136 -0
  182. aria_code/apps/cli/intent_router.py +305 -0
  183. aria_code/apps/cli/intent_shadow.py +72 -0
  184. aria_code/apps/cli/intent_signals.py +347 -0
  185. aria_code/apps/cli/lifecycle_hooks.py +48 -0
  186. aria_code/apps/cli/local_tool_schemas.py +550 -0
  187. aria_code/apps/cli/main.py +39 -0
  188. aria_code/apps/cli/market_hours.py +100 -0
  189. aria_code/apps/cli/market_metadata.py +135 -0
  190. aria_code/apps/cli/market_universe.py +487 -0
  191. aria_code/apps/cli/message_processing.py +310 -0
  192. aria_code/apps/cli/mixin_binding.py +32 -0
  193. aria_code/apps/cli/model_catalog.py +652 -0
  194. aria_code/apps/cli/model_probe.py +169 -0
  195. aria_code/apps/cli/model_retirement.py +65 -0
  196. aria_code/apps/cli/native_branding.py +12 -0
  197. aria_code/apps/cli/native_bridge.py +118 -0
  198. aria_code/apps/cli/native_entry.py +58 -0
  199. aria_code/apps/cli/pdf_report.py +977 -0
  200. aria_code/apps/cli/plan_mode.py +139 -0
  201. aria_code/apps/cli/plotly_html.py +15 -0
  202. aria_code/apps/cli/prediction_feedback.py +202 -0
  203. aria_code/apps/cli/preflight.py +502 -0
  204. aria_code/apps/cli/project_aria.py +60 -0
  205. aria_code/apps/cli/prompt_assembly.py +78 -0
  206. aria_code/apps/cli/prompts/__init__.py +0 -0
  207. aria_code/apps/cli/prompts/coding.py +71 -0
  208. aria_code/apps/cli/prompts/select.py +193 -0
  209. aria_code/apps/cli/prompts/system_prompts.py +562 -0
  210. aria_code/apps/cli/prompts/ui.py +436 -0
  211. aria_code/apps/cli/provider_endpoints.py +237 -0
  212. aria_code/apps/cli/provider_keys.py +73 -0
  213. aria_code/apps/cli/providers/__init__.py +1 -0
  214. aria_code/apps/cli/providers/base.py +775 -0
  215. aria_code/apps/cli/providers/chat_routing.py +139 -0
  216. aria_code/apps/cli/providers/collaboration.py +98 -0
  217. aria_code/apps/cli/providers/llm/__init__.py +1 -0
  218. aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
  219. aria_code/apps/cli/providers/llm/ollama_stream.py +1574 -0
  220. aria_code/apps/cli/providers/llm/response_cache.py +60 -0
  221. aria_code/apps/cli/providers/llm/sse_stream.py +298 -0
  222. aria_code/apps/cli/providers/runtime_bridge.py +586 -0
  223. aria_code/apps/cli/providers/vertexai_stream.py +592 -0
  224. aria_code/apps/cli/release_download.py +159 -0
  225. aria_code/apps/cli/review_runner.py +111 -0
  226. aria_code/apps/cli/runtime_consumer.py +769 -0
  227. aria_code/apps/cli/sandbox.py +32 -0
  228. aria_code/apps/cli/session_export.py +120 -0
  229. aria_code/apps/cli/session_jsonl.py +208 -0
  230. aria_code/apps/cli/session_refresh.py +123 -0
  231. aria_code/apps/cli/session_store.py +125 -0
  232. aria_code/apps/cli/skills_catalog.py +273 -0
  233. aria_code/apps/cli/task_isolation.py +211 -0
  234. aria_code/apps/cli/terminal_chrome.py +412 -0
  235. aria_code/apps/cli/todo_tracker.py +190 -0
  236. aria_code/apps/cli/tool_executor.py +932 -0
  237. aria_code/apps/cli/tool_registry.py +34 -0
  238. aria_code/apps/cli/tool_scope.py +113 -0
  239. aria_code/apps/cli/tools/__init__.py +40 -0
  240. aria_code/apps/cli/tools/context.py +46 -0
  241. aria_code/apps/cli/tools/file_tools.py +181 -0
  242. aria_code/apps/cli/tools/market_tools.py +626 -0
  243. aria_code/apps/cli/tools/notebook_tools.py +111 -0
  244. aria_code/apps/cli/tools/system_tools.py +656 -0
  245. aria_code/apps/cli/tools/write_tools.py +961 -0
  246. aria_code/apps/cli/tradingview_bridge.py +529 -0
  247. aria_code/apps/cli/transactions.py +269 -0
  248. aria_code/apps/cli/turn_planning.py +137 -0
  249. aria_code/apps/cli/update_check.py +257 -0
  250. aria_code/apps/cli/updater.py +373 -0
  251. aria_code/apps/cli/utils/__init__.py +0 -0
  252. aria_code/apps/cli/utils/market_detect.py +1663 -0
  253. aria_code/apps/cli/web_login.py +136 -0
  254. aria_code/apps/cli/workflow_runner.py +127 -0
  255. aria_code/apps/cli/workspace_route.py +121 -0
  256. aria_code/apps/daemon/README.md +14 -0
  257. aria_code/apps/vscode/README.md +115 -0
  258. aria_code/apps/vscode/package.json +70 -0
  259. aria_code/aria_cli.py +5244 -0
  260. aria_code/aria_daemon.py +1372 -0
  261. aria_code/aria_feishu_bot.py +16 -0
  262. aria_code/aria_mcp_server.py +61 -0
  263. aria_code/aria_relay_client.py +4 -0
  264. aria_code/aria_telegram_bot.py +11 -0
  265. aria_code/ariarc.py +385 -0
  266. aria_code/artifacts.py +581 -0
  267. aria_code/backtest_engine.py +4 -0
  268. aria_code/backtest_report.py +4 -0
  269. aria_code/brokers/__init__.py +78 -0
  270. aria_code/brokers/automation.py +193 -0
  271. aria_code/brokers/base.py +231 -0
  272. aria_code/brokers/capabilities.py +264 -0
  273. aria_code/brokers/cn/__init__.py +10 -0
  274. aria_code/brokers/cn/easytrader_broker.py +193 -0
  275. aria_code/brokers/cn/futu_broker.py +194 -0
  276. aria_code/brokers/cn/longbridge_broker.py +190 -0
  277. aria_code/brokers/cn/tiger_broker.py +196 -0
  278. aria_code/brokers/cn/xtquant_broker.py +225 -0
  279. aria_code/brokers/config.py +414 -0
  280. aria_code/brokers/intl/__init__.py +5 -0
  281. aria_code/brokers/intl/alpaca_broker.py +183 -0
  282. aria_code/brokers/intl/ibkr_broker.py +215 -0
  283. aria_code/brokers/intl/trading212_broker.py +183 -0
  284. aria_code/brokers/intl/webull_broker.py +156 -0
  285. aria_code/brokers/paper_broker.py +266 -0
  286. aria_code/brokers/planning.py +296 -0
  287. aria_code/brokers/registry.py +213 -0
  288. aria_code/brokers/trading.py +267 -0
  289. aria_code/canva_client.py +4 -0
  290. aria_code/change_store.py +127 -0
  291. aria_code/clients/__init__.py +0 -0
  292. aria_code/clients/aliyun_data_client.py +660 -0
  293. aria_code/clients/aria_relay_client.py +16 -0
  294. aria_code/clients/canva_client.py +284 -0
  295. aria_code/clients/figma_client.py +100 -0
  296. aria_code/clients/football_data_client.py +1671 -0
  297. aria_code/clients/kling_video_client.py +182 -0
  298. aria_code/clients/market_data_client.py +1985 -0
  299. aria_code/clients/openai_image_client.py +242 -0
  300. aria_code/clients/runway_video_client.py +157 -0
  301. aria_code/clients/warehouse_erp_client.py +105 -0
  302. aria_code/cloud_memory.py +190 -0
  303. aria_code/command_safety.py +19 -0
  304. aria_code/computer_use_server.py +276 -0
  305. aria_code/computer_use_tools.py +4 -0
  306. aria_code/dashboard_generator.py +579 -0
  307. aria_code/data_analysis_tools.py +4 -0
  308. aria_code/data_cleaner.py +483 -0
  309. aria_code/data_service.py +513 -0
  310. aria_code/datasources/__init__.py +23 -0
  311. aria_code/datasources/base.py +166 -0
  312. aria_code/datasources/router.py +253 -0
  313. aria_code/datasources/sec_statements.py +217 -0
  314. aria_code/datasources/sources/__init__.py +15 -0
  315. aria_code/datasources/sources/akshare_source.py +269 -0
  316. aria_code/datasources/sources/alpha_vantage_source.py +203 -0
  317. aria_code/datasources/sources/edgar_source.py +219 -0
  318. aria_code/datasources/sources/finnhub_source.py +198 -0
  319. aria_code/datasources/sources/fred_source.py +223 -0
  320. aria_code/datasources/sources/tushare_source.py +143 -0
  321. aria_code/datasources/sources/web_scraper_source.py +278 -0
  322. aria_code/datasources/sources/world_bank_source.py +206 -0
  323. aria_code/datasources/sources/yfinance_source.py +152 -0
  324. aria_code/doctor.py +860 -0
  325. aria_code/domain/__init__.py +0 -0
  326. aria_code/domain/_finance_core/__init__.py +5 -0
  327. aria_code/domain/_finance_core/backtest_engine.py +325 -0
  328. aria_code/domain/_finance_core/performance_stats.py +101 -0
  329. aria_code/domain/backtest_engine.py +75 -0
  330. aria_code/domain/backtest_report.py +488 -0
  331. aria_code/domain/finance_formulas.py +389 -0
  332. aria_code/domain/performance_stats.py +6 -0
  333. aria_code/domain/portfolio_ledger.py +307 -0
  334. aria_code/domain/strategy_vault.py +421 -0
  335. aria_code/evals/__init__.py +37 -0
  336. aria_code/evals/behavior.py +113 -0
  337. aria_code/evals/gap.py +93 -0
  338. aria_code/evals/harness.py +949 -0
  339. aria_code/evals/review_probe.py +132 -0
  340. aria_code/evals/runner.py +425 -0
  341. aria_code/evals/scorecard.py +322 -0
  342. aria_code/evals/summary.py +125 -0
  343. aria_code/external_agent_runner.py +115 -0
  344. aria_code/figma_client.py +4 -0
  345. aria_code/file_analysis_tools.py +4 -0
  346. aria_code/finance_formulas.py +4 -0
  347. aria_code/football_data_client.py +4 -0
  348. aria_code/image_gen_tools.py +4 -0
  349. aria_code/intent_classifier.py +459 -0
  350. aria_code/kling_video_client.py +4 -0
  351. aria_code/licensing.py +151 -0
  352. aria_code/local_finance_tools.py +4 -0
  353. aria_code/local_image_provider.py +4 -0
  354. aria_code/local_llm_provider.py +4 -0
  355. aria_code/macro_tools.py +4 -0
  356. aria_code/markdown_pdf.py +289 -0
  357. aria_code/market_data_client.py +4 -0
  358. aria_code/mcp_client.py +744 -0
  359. aria_code/memory_manager.py +273 -0
  360. aria_code/model_capability.py +512 -0
  361. aria_code/notification_tools.py +4 -0
  362. aria_code/openai_image_client.py +4 -0
  363. aria_code/packages/__init__.py +26 -0
  364. aria_code/packages/adk_bridge/__init__.py +11 -0
  365. aria_code/packages/adk_bridge/code_review_tools.py +49 -0
  366. aria_code/packages/adk_bridge/market_tools.py +125 -0
  367. aria_code/packages/aria_agents/__init__.py +5 -0
  368. aria_code/packages/aria_agents/manifest.py +78 -0
  369. aria_code/packages/aria_core/__init__.py +34 -0
  370. aria_code/packages/aria_core/architecture.py +269 -0
  371. aria_code/packages/aria_core/export.py +147 -0
  372. aria_code/packages/aria_core/manifest.py +65 -0
  373. aria_code/packages/aria_core/paths.py +57 -0
  374. aria_code/packages/aria_core/secure_file.py +68 -0
  375. aria_code/packages/aria_infra/__init__.py +15 -0
  376. aria_code/packages/aria_infra/arthera.py +58 -0
  377. aria_code/packages/aria_infra/doctor.py +246 -0
  378. aria_code/packages/aria_infra/product.py +40 -0
  379. aria_code/packages/aria_mcp/__init__.py +25 -0
  380. aria_code/packages/aria_mcp/bridge.py +232 -0
  381. aria_code/packages/aria_mcp/circuit.py +82 -0
  382. aria_code/packages/aria_mcp/config.py +162 -0
  383. aria_code/packages/aria_mcp/installer.py +95 -0
  384. aria_code/packages/aria_mcp/server.py +1705 -0
  385. aria_code/packages/aria_mcp/tools.py +81 -0
  386. aria_code/packages/aria_sdk/__init__.py +19 -0
  387. aria_code/packages/aria_sdk/client.py +393 -0
  388. aria_code/packages/aria_sdk/providers.py +70 -0
  389. aria_code/packages/aria_sdk/streaming.py +75 -0
  390. aria_code/packages/aria_sdk/types.py +86 -0
  391. aria_code/packages/aria_services/__init__.py +92 -0
  392. aria_code/packages/aria_services/cache.py +60 -0
  393. aria_code/packages/aria_services/context.py +356 -0
  394. aria_code/packages/aria_services/context_checkpoints.py +157 -0
  395. aria_code/packages/aria_services/data.py +11 -0
  396. aria_code/packages/aria_services/provider_health.py +304 -0
  397. aria_code/packages/aria_services/references.py +247 -0
  398. aria_code/packages/aria_services/registry.py +249 -0
  399. aria_code/packages/aria_services/research_protocol.py +137 -0
  400. aria_code/packages/aria_services/research_quality.py +186 -0
  401. aria_code/packages/aria_services/settings.py +160 -0
  402. aria_code/packages/aria_services/usage.py +151 -0
  403. aria_code/packages/aria_skills/__init__.py +46 -0
  404. aria_code/packages/aria_skills/catalog.py +149 -0
  405. aria_code/packages/aria_skills/loader.py +455 -0
  406. aria_code/packages/aria_skills/registry.py +84 -0
  407. aria_code/packages/aria_tools/__init__.py +5 -0
  408. aria_code/packages/aria_tools/financial/__init__.py +5 -0
  409. aria_code/packages/aria_tools/financial/_paths.py +92 -0
  410. aria_code/packages/aria_tools/registry.py +129 -0
  411. aria_code/packages/quant_engine/__init__.py +33 -0
  412. aria_code/packages/quant_engine/_namespace.py +24 -0
  413. aria_code/packages/quant_engine/backtest/__init__.py +10 -0
  414. aria_code/packages/quant_engine/backtest/core.py +53 -0
  415. aria_code/packages/quant_engine/backtest/engine.py +102 -0
  416. aria_code/packages/quant_engine/portfolio/__init__.py +9 -0
  417. aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
  418. aria_code/packages/quant_engine/sports/__init__.py +76 -0
  419. aria_code/packages/quant_engine/sports/calibrator.py +354 -0
  420. aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
  421. aria_code/packages/quant_engine/sports/elo.py +300 -0
  422. aria_code/packages/quant_engine/sports/form.py +188 -0
  423. aria_code/packages/quant_engine/sports/h2h.py +195 -0
  424. aria_code/packages/quant_engine/sports/predictor.py +312 -0
  425. aria_code/packages/quant_engine/sports/tracker.py +665 -0
  426. aria_code/packages/quant_engine/stochastic/__init__.py +31 -0
  427. aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  428. aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
  429. aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  430. aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  431. aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
  432. aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  433. aria_code/packs/__init__.py +85 -0
  434. aria_code/packs/base.py +168 -0
  435. aria_code/packs/finance/__init__.py +250 -0
  436. aria_code/packs/healthcare/__init__.py +40 -0
  437. aria_code/packs/logistics/__init__.py +171 -0
  438. aria_code/packs/payments/__init__.py +139 -0
  439. aria_code/packs/realty/__init__.py +190 -0
  440. aria_code/packs/registry.py +202 -0
  441. aria_code/packs/rules.py +90 -0
  442. aria_code/plan_utils.py +194 -0
  443. aria_code/plugin_loader.py +328 -0
  444. aria_code/portfolio_ledger.py +4 -0
  445. aria_code/preview_server.py +449 -0
  446. aria_code/privacy/__init__.py +5 -0
  447. aria_code/privacy/feedback.py +123 -0
  448. aria_code/project_review.py +278 -0
  449. aria_code/project_review_api.py +235 -0
  450. aria_code/project_review_server.py +18 -0
  451. aria_code/project_tools.py +4 -0
  452. aria_code/providers/__init__.py +30 -0
  453. aria_code/providers/google_video_provider.py +256 -0
  454. aria_code/providers/llm/__init__.py +19 -0
  455. aria_code/providers/llm/anthropic.py +184 -0
  456. aria_code/providers/llm/arthera_gateway.py +90 -0
  457. aria_code/providers/llm/autoconfig.py +213 -0
  458. aria_code/providers/llm/base.py +139 -0
  459. aria_code/providers/llm/custom.py +158 -0
  460. aria_code/providers/llm/ollama.py +137 -0
  461. aria_code/providers/llm/openai_compat.py +350 -0
  462. aria_code/providers/llm/registry.py +457 -0
  463. aria_code/providers/local_image_provider.py +202 -0
  464. aria_code/providers/local_llm_provider.py +549 -0
  465. aria_code/providers/ollama_models.py +79 -0
  466. aria_code/providers/openai_params.py +32 -0
  467. aria_code/providers/tool_messages.py +100 -0
  468. aria_code/realty_data_tools.py +4 -0
  469. aria_code/report_exporters.py +150 -0
  470. aria_code/report_generator.py +1764 -0
  471. aria_code/review_service.py +497 -0
  472. aria_code/runtime/__init__.py +176 -0
  473. aria_code/runtime/acceptance.py +632 -0
  474. aria_code/runtime/agent_loop.py +1851 -0
  475. aria_code/runtime/agent_profiles.py +75 -0
  476. aria_code/runtime/approval.py +71 -0
  477. aria_code/runtime/baseline.py +219 -0
  478. aria_code/runtime/budget.py +232 -0
  479. aria_code/runtime/checkpoints.py +481 -0
  480. aria_code/runtime/consult.py +65 -0
  481. aria_code/runtime/contract.py +282 -0
  482. aria_code/runtime/delivery.py +432 -0
  483. aria_code/runtime/events.py +113 -0
  484. aria_code/runtime/gateway.py +167 -0
  485. aria_code/runtime/lsp.py +484 -0
  486. aria_code/runtime/native_index.py +153 -0
  487. aria_code/runtime/orchestrator.py +55 -0
  488. aria_code/runtime/processes.py +343 -0
  489. aria_code/runtime/project_graph.py +614 -0
  490. aria_code/runtime/repo_map.py +712 -0
  491. aria_code/runtime/research_bundle.py +105 -0
  492. aria_code/runtime/review.py +269 -0
  493. aria_code/runtime/run_state.py +127 -0
  494. aria_code/runtime/run_store.py +522 -0
  495. aria_code/runtime/scoped_runs.py +113 -0
  496. aria_code/runtime/self_healing.py +422 -0
  497. aria_code/runtime/semantic_diff.py +171 -0
  498. aria_code/runtime/subagent.py +721 -0
  499. aria_code/runtime/symbol_edit.py +117 -0
  500. aria_code/runtime/task_brief.py +46 -0
  501. aria_code/runtime/task_context.py +145 -0
  502. aria_code/runtime/task_ledger.py +108 -0
  503. aria_code/runtime/task_orchestrator.py +90 -0
  504. aria_code/runtime/task_outcome.py +33 -0
  505. aria_code/runtime/task_verification.py +266 -0
  506. aria_code/runtime/task_worktree.py +448 -0
  507. aria_code/runtime/tool_executor.py +329 -0
  508. aria_code/runtime/tool_policy.py +107 -0
  509. aria_code/runtime/transactions.py +216 -0
  510. aria_code/runtime/windows_job.py +115 -0
  511. aria_code/runtime/workflows.py +222 -0
  512. aria_code/runtime/worktrees.py +215 -0
  513. aria_code/runway_video_client.py +4 -0
  514. aria_code/safety/__init__.py +27 -0
  515. aria_code/safety/client_isolation.py +178 -0
  516. aria_code/safety/permissions.py +339 -0
  517. aria_code/safety/risk.py +699 -0
  518. aria_code/safety/sandbox.py +148 -0
  519. aria_code/safety/service.py +102 -0
  520. aria_code/service_endpoints.py +14 -0
  521. aria_code/setup_wizard.py +684 -0
  522. aria_code/spreadsheet_tools.py +4 -0
  523. aria_code/strategy_vault.py +4 -0
  524. aria_code/tools/__init__.py +0 -0
  525. aria_code/tools/artifact_tools.py +252 -0
  526. aria_code/tools/broker_tools.py +92 -0
  527. aria_code/tools/build_quant_engine.py +97 -0
  528. aria_code/tools/code_audit_tools.py +192 -0
  529. aria_code/tools/computer_use_tools.py +509 -0
  530. aria_code/tools/data_analysis_tools.py +831 -0
  531. aria_code/tools/enterprise_finance_tools.py +202 -0
  532. aria_code/tools/extended_tools.py +240 -0
  533. aria_code/tools/file_analysis_tools.py +880 -0
  534. aria_code/tools/image_gen_tools.py +114 -0
  535. aria_code/tools/local_finance_tools.py +3488 -0
  536. aria_code/tools/logistics_carriers.py +11 -0
  537. aria_code/tools/logistics_inventory.py +11 -0
  538. aria_code/tools/logistics_io.py +11 -0
  539. aria_code/tools/logistics_tenancy.py +11 -0
  540. aria_code/tools/logistics_tools.py +11 -0
  541. aria_code/tools/macro_tools.py +412 -0
  542. aria_code/tools/notification_tools.py +249 -0
  543. aria_code/tools/project_tools.py +525 -0
  544. aria_code/tools/realty_data_tools.py +662 -0
  545. aria_code/tools/spreadsheet_tools.py +399 -0
  546. aria_code/tools/stripe_tools.py +115 -0
  547. aria_code/tools/video_tools.py +500 -0
  548. aria_code/training/__init__.py +1 -0
  549. aria_code/training/compare.py +117 -0
  550. aria_code/training/prelude_commands.py +111 -0
  551. aria_code/training/prelude_eval.py +390 -0
  552. aria_code/training/prelude_real.py +183 -0
  553. aria_code/training/prelude_train.py +248 -0
  554. aria_code/training/sft_dataset.py +230 -0
  555. aria_code/training/trajectory_dataset.py +263 -0
  556. aria_code/ui/__init__.py +105 -0
  557. aria_code/ui/banner.py +404 -0
  558. aria_code/ui/completer.py +571 -0
  559. aria_code/ui/console.py +285 -0
  560. aria_code/ui/input_box.py +505 -0
  561. aria_code/ui/mentions.py +206 -0
  562. aria_code/ui/picker.py +248 -0
  563. aria_code/ui/render/__init__.py +11 -0
  564. aria_code/ui/render/actions.py +297 -0
  565. aria_code/ui/render/finance.py +1732 -0
  566. aria_code/ui/render/market.py +253 -0
  567. aria_code/ui/render/output.py +954 -0
  568. aria_code/ui/render/responsive.py +75 -0
  569. aria_code/ui/render/team.py +367 -0
  570. aria_code/ui/robot.py +239 -0
  571. aria_code/ui/startup_dashboard.py +110 -0
  572. aria_code/video_analysis.py +147 -0
  573. aria_code/video_editor.py +518 -0
  574. aria_code/video_workflow/__init__.py +8 -0
  575. aria_code/video_workflow/__main__.py +61 -0
  576. aria_code/video_workflow/engines/__init__.py +18 -0
  577. aria_code/video_workflow/engines/base.py +69 -0
  578. aria_code/video_workflow/engines/custom_model.py +204 -0
  579. aria_code/video_workflow/engines/google_veo.py +94 -0
  580. aria_code/video_workflow/engines/local_engine.py +71 -0
  581. aria_code/video_workflow/engines/registry.py +98 -0
  582. aria_code/video_workflow/google_models.py +278 -0
  583. aria_code/video_workflow/music/__init__.py +5 -0
  584. aria_code/video_workflow/music/foley_service.py +132 -0
  585. aria_code/video_workflow/music/service.py +212 -0
  586. aria_code/video_workflow/pipeline.py +620 -0
  587. aria_code/video_workflow/plan.py +202 -0
  588. aria_code/video_workflow/session.py +297 -0
  589. aria_code/workspace/__init__.py +6 -0
  590. aria_code/workspace/files.py +219 -0
  591. aria_code/workspace/verify.py +167 -0
  592. aria_code/workspace_changes.py +199 -0
  593. aria_code/workspace_service.py +196 -0
  594. aria_code/workspace_tools.py +150 -0
  595. aria_code-0.167.0.dist-info/METADATA +330 -0
  596. aria_code-0.167.0.dist-info/RECORD +601 -0
  597. aria_code-0.167.0.dist-info/WHEEL +5 -0
  598. aria_code-0.167.0.dist-info/entry_points.txt +5 -0
  599. aria_code-0.167.0.dist-info/licenses/LICENSE +201 -0
  600. aria_code-0.167.0.dist-info/licenses/NOTICE +49 -0
  601. aria_code-0.167.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,932 @@
1
+
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import asyncio
6
+ import pathlib
7
+ import subprocess
8
+ import os
9
+ import re as re_module
10
+ import time
11
+ import sys
12
+ import re
13
+ from typing import Dict, Any, Optional
14
+ from datetime import datetime
15
+
16
+ from aria_code.runtime import ApprovalDecision, apply_approval_decision
17
+ from aria_code.runtime.tool_policy import check_tool_policy
18
+
19
+ try:
20
+ from rich import box as rich_box
21
+ from rich.panel import Panel
22
+ except ImportError: # The legacy CLI injects these when Rich is available.
23
+ rich_box = None
24
+ Panel = None
25
+
26
+ # This module is imported by ``aria_cli`` and its public functions are rebound
27
+ # to that module's globals for backward compatibility. Keep safe defaults so
28
+ # the extracted module can still be imported, linted and type-checked on its
29
+ # own. The defaults are deliberately conservative; interactive execution uses
30
+ # the values owned by ``aria_cli`` after rebinding.
31
+ _ACTIVE_NETWORK_ENABLED = [True]
32
+ _ARIA_BOT_MODE = False
33
+ _CONFIRM_TOOLS: set[str] = set()
34
+ _HAS_JSON_HOOKS = False
35
+ _JSON_HOOKS: dict[str, object] = {}
36
+ # Real values, not placeholders. These two used to be empty stubs here and were
37
+ # defined for real in aria_cli.py, so execute_aria_tool only cached when it ran
38
+ # as the rebound copy with aria_cli's globals — and a direct import of it
39
+ # silently got a TTL table with nothing in it, i.e. no caching at all. aria_cli
40
+ # imports them from here now, so both copies share one cache and one config.
41
+ _CACHE_TTL: dict[str, int] = {
42
+ "get_market_data": 30, "get_market_history": 300, "get_crypto_data": 30, "get_forex_data": 30,
43
+ "get_commodities_data": 60, "get_bonds_data": 60, "get_futures_data": 60,
44
+ "get_news": 300, "get_sector_performance": 60, "get_market_overview": 60,
45
+ }
46
+ _TOOL_CACHE: dict[str, tuple] = {} # key -> (result, timestamp)
47
+ _auto_approve_session = False
48
+ _session_always_allow: set[str] = set()
49
+ _session_command_prefixes: set[tuple[str, ...]] = set()
50
+
51
+
52
+ def _command_matches_session_prefix(command: str) -> bool:
53
+ return False
54
+
55
+
56
+ def _command_approval_prefix(command: str) -> tuple[str, ...]:
57
+ return ()
58
+
59
+
60
+ def _arrow_select(options, selected: int = 0, title: str = "", **_kwargs) -> int:
61
+ return len(options) - 1
62
+
63
+
64
+ def _fire_json_hook(*args, **kwargs) -> bool:
65
+ return True
66
+
67
+ from aria_code.apps.cli.tools.system_tools import (
68
+ tool_run_command as _src_run_command,
69
+ tool_web_fetch as _src_web_fetch,
70
+ tool_github as _src_github,
71
+ )
72
+ from aria_code.apps.cli.tools.notebook_tools import (
73
+ tool_glob as _src_glob,
74
+ tool_notebook_read as _src_notebook_read,
75
+ tool_notebook_edit as _src_notebook_edit,
76
+ )
77
+ from aria_code.apps.cli.tools.file_tools import (
78
+ tool_read_file as _src_read_file,
79
+ tool_list_files as _src_list_files,
80
+ tool_search_code as _src_search_code,
81
+ )
82
+ from aria_code.apps.cli.tools.market_tools import (
83
+ tool_get_market_data as _src_get_market_data,
84
+ tool_get_market_history as _src_get_market_history,
85
+ tool_broker_query as _src_broker_query,
86
+ tool_broker_order as _src_broker_order,
87
+ )
88
+
89
+
90
+
91
+
92
+ def _g(name):
93
+ from aria_code import aria_cli
94
+ return getattr(aria_cli, name)
95
+
96
+ def _tool_analyze_file(params: dict) -> dict:
97
+ """Parse & analyse a local document/image (pdf/docx/xlsx/csv/image/…)."""
98
+ from aria_code.file_analysis_tools import tool_analyze_file as _f
99
+ return _f(params)
100
+ def _tool_read_file(params: dict) -> dict:
101
+ """Thin shim — implementation in apps/cli/tools/file_tools.py."""
102
+ return _src_read_file(params)
103
+ def _strip_markdown_fences(content: str) -> str:
104
+ """Thin shim — implementation in apps/cli/tools/write_tools.py."""
105
+ from aria_code.apps.cli.tools.write_tools import _strip_markdown_fences as _f
106
+ return _f(content)
107
+ def _auto_fix_python(content: str, path: str) -> str:
108
+ """Thin shim — implementation in apps/cli/tools/write_tools.py."""
109
+ from aria_code.apps.cli.tools.write_tools import _auto_fix_python as _f
110
+ return _f(content, path)
111
+ def _write_policy_confirm(p: pathlib.Path, content: str, existed: bool) -> tuple:
112
+ """Thin shim — implementation in apps/cli/tools/write_tools.py."""
113
+ from aria_code.apps.cli.tools.write_tools import _write_policy_confirm as _f
114
+ return _f(p, content, existed)
115
+ def _tool_write_file(params: dict) -> dict:
116
+ """Thin shim — implementation in apps/cli/tools/write_tools.py."""
117
+ from aria_code.apps.cli.tools.write_tools import tool_write_file as _f
118
+ return _f(params)
119
+ def _tool_edit_file(params: dict) -> dict:
120
+ """Thin shim — implementation in apps/cli/tools/write_tools.py."""
121
+ from aria_code.apps.cli.tools.write_tools import tool_edit_file as _f
122
+ return _f(params)
123
+ def _tool_multi_edit(params: dict) -> dict:
124
+ """Thin shim — implementation in apps/cli/tools/write_tools.py."""
125
+ from aria_code.apps.cli.tools.write_tools import tool_multi_edit as _f
126
+ return _f(params)
127
+ def _tool_update_todos(params: dict) -> dict:
128
+ """Thin shim — implementation in apps/cli/todo_tracker.py."""
129
+ from aria_code.apps.cli.todo_tracker import update_todos as _f
130
+ return _f(params)
131
+ def _tool_list_files(params: dict) -> dict:
132
+ """Thin shim — implementation in apps/cli/tools/file_tools.py."""
133
+ return _src_list_files(params)
134
+ def _tool_search_code(params: dict) -> dict:
135
+ """Thin shim — implementation in apps/cli/tools/file_tools.py."""
136
+ return _src_search_code(params)
137
+ def _tool_run_command(params: dict) -> dict:
138
+ """Run a shell command — thin wrapper supplying global defaults."""
139
+ params.setdefault("permission_mode", _g("_ACTIVE_PERMISSION_MODE")[0])
140
+ params.setdefault("network_enabled", _g("_ACTIVE_NETWORK_ENABLED")[0])
141
+ return _src_run_command(params, console=_g("console"), has_rich=_g("HAS_RICH"),
142
+ quiet=bool(_g("_ACTION_VIEW_ACTIVE")[0]))
143
+ def _tool_web_fetch(params: dict) -> dict:
144
+ return _src_web_fetch(params)
145
+ def _tool_github(params: dict) -> dict:
146
+ params.setdefault("permission_mode", _g("_ACTIVE_PERMISSION_MODE")[0])
147
+ params.setdefault("network_enabled", _g("_ACTIVE_NETWORK_ENABLED")[0])
148
+ return _src_github(params, console=_g("console"), has_rich=_g("HAS_RICH"))
149
+ def _tool_glob(params: dict) -> dict:
150
+ return _src_glob(params)
151
+ def _tool_notebook_read(params: dict) -> dict:
152
+ return _src_notebook_read(params)
153
+ def _tool_notebook_edit(params: dict) -> dict:
154
+ return _src_notebook_edit(params)
155
+ def _tool_broker_query(params: dict) -> dict:
156
+ return _src_broker_query(params)
157
+ def _tool_broker_order(params: dict) -> dict:
158
+ return _src_broker_order(params)
159
+ def _tool_get_market_data(params: dict) -> dict:
160
+ return _src_get_market_data(params)
161
+ def _tool_get_market_history(params: dict) -> dict:
162
+ return _src_get_market_history(params)
163
+ def _todo_schema():
164
+ from aria_code.apps.cli.todo_tracker import UPDATE_TODOS_SCHEMA
165
+ return UPDATE_TODOS_SCHEMA
166
+ def _wrap_bare_schemas(bare: list) -> list:
167
+ """Convert Anthropic-style {name, description, parameters} schemas into the
168
+ OpenAI {type:function, function:{…}} envelope used by _g("LOCAL_TOOL_SCHEMAS").
169
+
170
+ The subagent and LSP modules declare schemas in the bare form; without this
171
+ wrapping their tools execute but are invisible to the model (it is never
172
+ told they exist).
173
+ """
174
+ wrapped = []
175
+ for s in bare or []:
176
+ if "function" in s: # already enveloped
177
+ wrapped.append(s)
178
+ else:
179
+ wrapped.append({"type": "function", "function": s})
180
+ return wrapped
181
+ def _dedup_tool_schemas() -> None:
182
+ """Drop duplicate tool schemas, keeping the LAST occurrence by name.
183
+
184
+ Some tools (e.g. web_fetch, get_market_data) are declared both by the
185
+ finance-tools registry and the static schema block. The static block runs
186
+ after and carries the richer description, so keeping the last copy wins.
187
+
188
+ Schemas arrive in two shapes: the OpenAI-style ``{"type": "function",
189
+ "function": {...}}`` wrapper and the bare ``{"name", "description",
190
+ "parameters"}`` form that several register_* helpers emit. Reading only
191
+ the wrapped shape gave a bare schema an empty name, and the ``if name``
192
+ guard then dropped it — silently, and only from the schema list, so the
193
+ handler stayed in LOCAL_TOOLS while the model was never told the tool
194
+ existed. analyze_logistics_data, analyze_stripe_data and
195
+ analyze_financial_statements were all unreachable this way: registered,
196
+ callable, and invisible.
197
+
198
+ It also normalises, which is why it reads both shapes and writes one.
199
+ Leaving the bare form in the list sends Ollama and the OpenAI-compatible
200
+ providers a schema they reject, and forces every consumer to handle two
201
+ shapes — one test already assumed the wrapper and broke on the other.
202
+ """
203
+ kept: dict = {}
204
+ for schema in _g("LOCAL_TOOL_SCHEMAS"):
205
+ name = (schema.get("function") or schema).get("name", "")
206
+ if not name:
207
+ continue # malformed: nothing can call it, nothing can describe it
208
+ kept[name] = schema if "function" in schema else {
209
+ "type": "function", "function": schema,
210
+ }
211
+ _g("LOCAL_TOOL_SCHEMAS")[:] = list(kept.values())
212
+ def _show_edit_preview(params: dict):
213
+ """Show a diff preview for edit_file (Claude Code style, Panel-boxed)."""
214
+ if _g("_ARIA_BOT_MODE"):
215
+ return
216
+ path = params.get("path", "")
217
+ old_str = params.get("old_string", params.get("old_str", ""))
218
+ new_str = params.get("new_string", params.get("new_str", ""))
219
+ if not path or not old_str:
220
+ return
221
+
222
+ p = pathlib.Path(path).expanduser().resolve()
223
+ short = p.name or "file tool"
224
+
225
+ if not _g("HAS_RICH"):
226
+ print(f"\n Edit file {short}")
227
+ return
228
+
229
+ body_parts: list = []
230
+ try:
231
+ content = p.read_text(errors="replace")
232
+ pos = content.find(old_str)
233
+ if pos >= 0:
234
+ line_num = content[:pos].count("\n") + 1
235
+ all_lines = content.splitlines()
236
+ old_lines = old_str.splitlines()
237
+ new_lines = new_str.splitlines()
238
+
239
+ # Context before (up to 2 lines)
240
+ ctx_start = max(0, line_num - 3)
241
+ for i in range(ctx_start, line_num - 1):
242
+ if i < len(all_lines):
243
+ body_parts.append(f"[dim]{i+1:4} {all_lines[i][:100]}[/dim]")
244
+
245
+ # Removed lines
246
+ for i, ol in enumerate(old_lines):
247
+ ln = line_num + i
248
+ body_parts.append(f"[red]{ln:4} - {ol[:100]}[/red]")
249
+
250
+ # Added lines
251
+ for i, nl in enumerate(new_lines):
252
+ ln = line_num + i
253
+ body_parts.append(f"[green]{ln:4} + {nl[:100]}[/green]")
254
+
255
+ # Context after (up to 2 lines)
256
+ after_start = line_num - 1 + len(old_lines)
257
+ for i in range(after_start, min(after_start + 2, len(all_lines))):
258
+ body_parts.append(f"[dim]{i+1:4} {all_lines[i][:100]}[/dim]")
259
+ else:
260
+ # String not found — fallback to plain diff lines
261
+ for ol in old_str.splitlines()[:6]:
262
+ body_parts.append(f"[red]- {ol[:100]}[/red]")
263
+ for nl in new_str.splitlines()[:6]:
264
+ body_parts.append(f"[green]+ {nl[:100]}[/green]")
265
+ except Exception:
266
+ for ol in old_str.splitlines()[:6]:
267
+ body_parts.append(f"[red]- {ol[:100]}[/red]")
268
+ for nl in new_str.splitlines()[:6]:
269
+ body_parts.append(f"[green]+ {nl[:100]}[/green]")
270
+
271
+ _g("console").print()
272
+ _g("console").print(Panel(
273
+ "\n".join(body_parts) if body_parts else "[dim](no preview)[/dim]",
274
+ title=f"[yellow]Edit file[/yellow] [dim]{short}[/dim]",
275
+ title_align="left",
276
+ border_style="yellow",
277
+ box=rich_box.ROUNDED,
278
+ padding=(0, 1),
279
+ ))
280
+ def _show_multi_edit_preview(params: dict):
281
+ """Show a compact preview of all edits in a multi_edit call."""
282
+ if _g("_ARIA_BOT_MODE"):
283
+ return
284
+ path = params.get("path", "")
285
+ edits = params.get("edits", [])
286
+ if not path or not edits:
287
+ return
288
+ if not _g("HAS_RICH"):
289
+ print(f"\n Multi-edit {path} ({len(edits)} edits)")
290
+ return
291
+ body_parts: list = []
292
+ for i, ed in enumerate(edits):
293
+ old_s = ed.get("old_string", ed.get("old_str", "")) if isinstance(ed, dict) else ""
294
+ new_s = ed.get("new_string", ed.get("new_str", "")) if isinstance(ed, dict) else ""
295
+ body_parts.append(f"[bold]#{i+1}[/bold]")
296
+ for ol in old_s.splitlines()[:3]:
297
+ body_parts.append(f"[red] - {ol[:96]}[/red]")
298
+ for nl in new_s.splitlines()[:3]:
299
+ body_parts.append(f"[green] + {nl[:96]}[/green]")
300
+ _g("console").print()
301
+ _g("console").print(Panel(
302
+ "\n".join(body_parts) if body_parts else "[dim](no preview)[/dim]",
303
+ title=f"[yellow]Multi-edit[/yellow] [dim]{pathlib.Path(path).name} · {len(edits)} edits[/dim]",
304
+ title_align="left",
305
+ border_style="yellow",
306
+ box=rich_box.ROUNDED,
307
+ padding=(0, 1),
308
+ ))
309
+ def _show_write_preview(params: dict):
310
+ """Show a content preview for write_file (Claude Code style, Panel-boxed)."""
311
+ if _g("_ARIA_BOT_MODE"):
312
+ return
313
+ path = params.get("path", "")
314
+ content = params.get("content", "")
315
+ if not path:
316
+ return
317
+ # Show cleaned content (without markdown fences)
318
+ content = _strip_markdown_fences(content)
319
+
320
+ p = pathlib.Path(path).expanduser().resolve()
321
+ short = p.name or "file tool"
322
+
323
+ existed = p.exists()
324
+ action = "Overwrite file" if existed else "Write new file"
325
+ action_color = "yellow" if existed else "green"
326
+ lines = content.count("\n") + 1
327
+
328
+ if not _g("HAS_RICH"):
329
+ print(f"\n {action} {short} ({lines} lines)")
330
+ return
331
+
332
+ if lines > 80:
333
+ body_parts = [
334
+ f"[dim]Large generated file: {lines} lines[/dim]",
335
+ "[dim]Preview suppressed; the full content will be written as an artifact/file.[/dim]",
336
+ "[dim]Use /read after creation if you need to inspect it.[/dim]",
337
+ ]
338
+ else:
339
+ preview_lines = content.splitlines()[:6]
340
+ body_parts = [f"[green]+ {pl[:100]}[/green]" for pl in preview_lines]
341
+ if lines > 6:
342
+ n = lines - 6
343
+ body_parts.append(f"[dim]… +{n} more line{'s' if n != 1 else ''}[/dim]")
344
+ body = "\n".join(body_parts)
345
+
346
+ _g("console").print()
347
+ _g("console").print(Panel(
348
+ body,
349
+ title=f"[{action_color}]{action}[/{action_color}] [dim]{short} ({lines} lines)[/dim]",
350
+ title_align="left",
351
+ border_style=action_color if existed else "dim",
352
+ box=rich_box.ROUNDED,
353
+ padding=(0, 1),
354
+ ))
355
+ def _apply_tool_approval(params: dict, decision: ApprovalDecision) -> dict:
356
+ """Apply approval state to CLI globals and execution params."""
357
+ # The two sets are only mutated in place, so only the rebound flag needs this.
358
+ global _auto_approve_session
359
+ if decision.auto_approve_session:
360
+ _auto_approve_session = True
361
+ if decision.tool_scope:
362
+ _session_always_allow.add(decision.tool_scope)
363
+ if decision.command_prefix:
364
+ _session_command_prefixes.add(tuple(decision.command_prefix))
365
+ return apply_approval_decision(params, decision)
366
+ _DENY_LABEL = "No, tell Aria what to do instead"
367
+
368
+
369
+ def _deny_help(zh: bool) -> str:
370
+ return "拒绝;可以说明想怎么做,Enter 直接停止" if zh else "decline; say what to do, or Enter to stop"
371
+
372
+
373
+ def _deny_with_feedback(chosen: bool) -> ApprovalDecision:
374
+ """A denial, carrying what the user wants instead when they say it.
375
+
376
+ With feedback the turn continues — the model reads it in place of the
377
+ call's result; with none (Enter, or Esc on the menu) it stops, as before.
378
+ """
379
+ if not chosen:
380
+ return ApprovalDecision.deny("user denied")
381
+ from aria_code.apps.cli.tools.write_tools import _ui_zh
382
+
383
+ prompt = (" 告诉 Aria 该怎么做(Enter 直接停止): " if _ui_zh()
384
+ else " Tell Aria what to do instead (Enter to stop): ")
385
+ try:
386
+ feedback = (_g("console").input(prompt) if _g("HAS_RICH") else input(prompt)).strip()
387
+ except (EOFError, KeyboardInterrupt, OSError):
388
+ feedback = ""
389
+ return ApprovalDecision.deny("user denied", feedback=feedback)
390
+
391
+
392
+ def _assess_for_approval(tool_name: str, params: dict):
393
+ """(assessment, requirement) for an approval; ("ask") if assessment fails."""
394
+ try:
395
+ import os as _os
396
+ from aria_code.safety.risk import approval_requirement, assess_tool
397
+
398
+ assessment = assess_tool(tool_name, params, root=_os.getcwd())
399
+ mode = _g("_ACTIVE_APPROVAL_MODE")[0]
400
+ level = _g("_ACTIVE_AUTO_APPROVE_LEVEL")[0]
401
+ return assessment, approval_requirement(assessment, mode=mode, auto_level=level)
402
+ except Exception:
403
+ return None, "ask"
404
+
405
+
406
+ def _show_risk_card(assessment) -> None:
407
+ if assessment is None or _g("_ARIA_BOT_MODE"):
408
+ return
409
+ from aria_code.ui.render.output import print_risk_card
410
+
411
+ print_risk_card(_g("console") if _g("HAS_RICH") else None, assessment)
412
+
413
+
414
+ def _note_auto_approval(tool_name: str, assessment) -> None:
415
+ """One dim line, so an approval that did not ask is still visible."""
416
+ if assessment is None or _g("_ARIA_BOT_MODE"):
417
+ return
418
+ text = f"auto-approved · L{assessment.level} {assessment.name} · {assessment.summary or tool_name}"
419
+ if _g("HAS_RICH"):
420
+ _g("console").print(f" [dim]✓ {text}[/dim]", highlight=False)
421
+ else:
422
+ print(f" ✓ {text}")
423
+
424
+
425
+ def _confirm_tool_execution_decision(tool_name: str, params: dict,
426
+ config_policy: str = None) -> ApprovalDecision:
427
+ """Ask user to confirm before executing a destructive tool.
428
+ Returns a structured approval decision.
429
+
430
+ For run_command: pre-flight policy check happens HERE, before showing the
431
+ picker. If the command would be blocked even with user approval (high-risk),
432
+ show error immediately. If medium-risk with 'safe' policy, offer to upgrade
433
+ policy inline so the user can act without leaving the flow.
434
+ """
435
+ if config_policy is None:
436
+ config_policy = _g("_ACTIVE_COMMAND_POLICY")[0]
437
+ # ── Persistent per-tool policy check (allowlist / denylist) ─────────────
438
+ _policy_verdict = check_tool_policy(tool_name)
439
+ if _policy_verdict == "deny":
440
+ if _g("HAS_RICH"):
441
+ _g("console").print(
442
+ f" [red]✗ 工具 '{tool_name}' 被永久黑名单拒绝[/red] "
443
+ f"[dim](/config policy remove {tool_name} 可解除)[/dim]"
444
+ )
445
+ else:
446
+ print(f" ✗ '{tool_name}' blocked by tool policy")
447
+ return ApprovalDecision.deny("blocked by tool policy (deny list)")
448
+
449
+ # ── Risk: what the call touches decides how it is approved ───────────────
450
+ # L4 is asked about every time — a standing "always allow" was given for
451
+ # something milder. Under approval_mode=risk, calls at or below the
452
+ # auto-approve level run without a prompt (see safety.risk).
453
+ _assessment, _requirement = _assess_for_approval(tool_name, params)
454
+ _standing_ok = _requirement != "always"
455
+ # Reading or driving the person's own screen is never covered by a standing
456
+ # policy or by "allow all tools"; only an explicit per-tool choice, and never
457
+ # from a chat bot, where whoever is typing is not at this computer.
458
+ from aria_code.safety.risk import DESKTOP_TOOLS as _DESKTOP_TOOLS
459
+ _desktop = tool_name in _DESKTOP_TOOLS
460
+ if _desktop and _g("_ARIA_BOT_MODE"):
461
+ return ApprovalDecision.deny("desktop control is not available from a chat bot")
462
+
463
+ if _standing_ok and not _desktop and _policy_verdict == "allow":
464
+ if tool_name == "run_command":
465
+ return ApprovalDecision.allow(policy=config_policy, user_approved=True)
466
+ return ApprovalDecision.allow()
467
+
468
+ if _standing_ok and _auto_approve_session and not _desktop:
469
+ # Still inject policy so run_command doesn't re-block
470
+ if tool_name == "run_command":
471
+ return ApprovalDecision.allow(policy=config_policy, user_approved=True)
472
+ return ApprovalDecision.allow()
473
+ if _standing_ok and tool_name == "run_command" and _command_matches_session_prefix(params.get("command", "")):
474
+ return ApprovalDecision.allow(policy="balanced", user_approved=True)
475
+ # Per-tool session allow — user previously chose "Always allow [tool] this session"
476
+ if _standing_ok and tool_name in _session_always_allow:
477
+ if tool_name == "run_command":
478
+ return ApprovalDecision.allow(policy=config_policy, user_approved=True)
479
+ return ApprovalDecision.allow()
480
+
481
+ # ── Plan mode: confirm ALL tools (even non-CONFIRM_TOOLS) ────────────────
482
+ if _g("_PLAN_MODE").active and tool_name not in _CONFIRM_TOOLS:
483
+ return _g("_PLAN_MODE").confirm_step(
484
+ tool_name, params,
485
+ console=_g("console"),
486
+ has_rich=_g("HAS_RICH"),
487
+ arrow_select_fn=_arrow_select,
488
+ )
489
+ # Ask-always policy: force confirmation even for normally silent tools
490
+ if _policy_verdict == "ask" and tool_name not in _CONFIRM_TOOLS:
491
+ _CONFIRM_TOOLS.add(tool_name) # promote for this call only
492
+
493
+ if tool_name not in _CONFIRM_TOOLS:
494
+ return ApprovalDecision.allow()
495
+
496
+ # ── JSON PreToolUse hook — can block execution ────────────────────────────
497
+ if _HAS_JSON_HOOKS and _JSON_HOOKS.get("PreToolUse"):
498
+ _allowed = _fire_json_hook(
499
+ "PreToolUse", tool=tool_name, params=params, hooks=_JSON_HOOKS,
500
+ )
501
+ if not _allowed:
502
+ return ApprovalDecision.deny("Blocked by PreToolUse hook")
503
+
504
+ if _requirement == "auto":
505
+ _note_auto_approval(tool_name, _assessment)
506
+ if tool_name == "run_command":
507
+ return ApprovalDecision.allow(policy="balanced", user_approved=True)
508
+ return ApprovalDecision.allow()
509
+
510
+ # ── Pre-flight for run_command ────────────────────────────────────────────
511
+ from aria_code.apps.cli.tools.write_tools import _ui_zh
512
+ zh = _ui_zh()
513
+ if tool_name == "run_command":
514
+ from aria_code.safety import classify_command_risk
515
+ from aria_code.safety.permissions import is_verification_command
516
+ cmd = params.get("command", "")
517
+ if isinstance(cmd, list):
518
+ import shlex as _shlex_tmp
519
+ cmd = _shlex_tmp.join(str(c) for c in cmd)
520
+ params["command"] = cmd
521
+ risk = classify_command_risk(cmd)
522
+ if risk != "high":
523
+ _show_risk_card(_assessment)
524
+
525
+ if risk == "high":
526
+ # Always block high-risk regardless of user approval
527
+ if _g("HAS_RICH"):
528
+ _g("console").print(Panel(
529
+ (f"[red]✗ 高风险命令已拒绝[/red]\n[dim]{cmd[:120]}[/dim]\n"
530
+ f"[dim]高风险操作(rm -rf / docker / sudo 等)需要在终端手动执行。[/dim]") if zh else
531
+ (f"[red]✗ High-risk command refused[/red]\n[dim]{cmd[:120]}[/dim]\n"
532
+ f"[dim]Run high-risk operations (rm -rf, docker, sudo…) yourself in a terminal.[/dim]"),
533
+ border_style="red", box=rich_box.ROUNDED, padding=(0, 1),
534
+ ))
535
+ else:
536
+ print(f" ✗ {'高风险命令已拒绝' if zh else 'High-risk command refused'}: {cmd[:80]}")
537
+ return ApprovalDecision.deny("high-risk command")
538
+
539
+ # Tests and type checks run under `safe` (see evaluate_command_policy);
540
+ # offering to "upgrade to balanced" for them misstated the policy.
541
+ if risk == "medium" and config_policy == "safe" and not is_verification_command(cmd):
542
+ # Show a richer picker that includes a "Allow & upgrade policy" option
543
+ if _g("HAS_RICH"):
544
+ _g("console").print()
545
+ _g("console").print(Panel(
546
+ f"[yellow]⚠ {'此命令需要 balanced 策略(当前: safe)' if zh else 'This command needs the balanced policy (current: safe)'}[/yellow]\n"
547
+ f"[dim]{cmd[:120]}[/dim]",
548
+ border_style="yellow", box=rich_box.ROUNDED, padding=(0, 1),
549
+ ))
550
+ _prefix = _command_approval_prefix(cmd)
551
+ _prefix_label = " ".join(_prefix)[:72] if _prefix else cmd[:72]
552
+ options = [
553
+ ("Allow once", "仅此次允许(不改变策略)" if zh else "this time only; the policy stays safe"),
554
+ ("Allow similar this session", f"{'本会话允许前缀' if zh else 'this session, for'}: {_prefix_label}"),
555
+ ("Allow & set balanced", "允许并升级策略(本会话有效)" if zh else "and use balanced for this session"),
556
+ (_DENY_LABEL, _deny_help(zh)),
557
+ ]
558
+ # Imported here: aria_cli rebinds this function to its own globals.
559
+ from aria_code.apps.cli.runtime_consumer import approval_subject as _subject
560
+ choice = _arrow_select(options, selected=0, title="",
561
+ collapse_to=_subject(tool_name, params),
562
+ shortcuts={"y": 0, "a": 1, "b": 2, "n": 3}, numbered=True)
563
+ if choice == 0:
564
+ return ApprovalDecision.allow(policy="balanced", user_approved=True)
565
+ if choice == 1:
566
+ return ApprovalDecision.allow(
567
+ policy="balanced",
568
+ user_approved=True,
569
+ command_prefix=_prefix,
570
+ )
571
+ if choice == 2:
572
+ return ApprovalDecision.allow(
573
+ policy="balanced",
574
+ user_approved=True,
575
+ upgrade_policy=True,
576
+ )
577
+ return _deny_with_feedback(choice == 3)
578
+
579
+ # ── Default confirmation for write_file / edit_file / low-risk run ────────
580
+ if tool_name == "edit_file":
581
+ # Resolve a symbol edit now, so the preview shows the real change and
582
+ # the approved params are the ones that run.
583
+ from aria_code.runtime.symbol_edit import resolve_symbol_edit
584
+ resolve_symbol_edit(params)
585
+ _show_edit_preview(params)
586
+ elif tool_name == "multi_edit":
587
+ _show_multi_edit_preview(params)
588
+ elif tool_name == "write_file":
589
+ _show_write_preview(params)
590
+ elif tool_name == "run_command":
591
+ # Header already printed by on_tool_call — just pass through policy
592
+ pass
593
+ if tool_name != "run_command":
594
+ _show_risk_card(_assessment)
595
+
596
+ _tool_label = ({"write_file": "写文件", "edit_file": "编辑文件", "multi_edit": "批量编辑", "run_command": "运行命令"}
597
+ if zh else {"write_file": "file writes", "edit_file": "file edits", "multi_edit": "multi-edits",
598
+ "run_command": "commands"}).get(tool_name, tool_name)
599
+ _command_prefix = _command_approval_prefix(params.get("command", "")) if tool_name == "run_command" else ()
600
+ _scope_label = (
601
+ f"Always allow {' '.join(_command_prefix)[:64]}"
602
+ if _command_prefix else f"Always allow {_tool_label}"
603
+ )
604
+ _scope_help = (
605
+ ("本会话内允许相同命令前缀" if zh else "for the rest of this session")
606
+ if _command_prefix else (f"本会话内自动允许所有 {_tool_label}" if zh else f"all {_tool_label} this session")
607
+ )
608
+ options = [
609
+ ("Yes", ""),
610
+ (_scope_label, _scope_help),
611
+ ("Yes, allow all tools", "本会话内所有工具自动允许" if zh else "every tool, this session"),
612
+ (_DENY_LABEL, _deny_help(zh)),
613
+ ]
614
+ if _desktop:
615
+ del options[2] # no blanket approval for the screen, mouse or keyboard
616
+ _deny_index = len(options) - 1
617
+ # Imported here: aria_cli rebinds this function to its own globals.
618
+ from aria_code.apps.cli.runtime_consumer import approval_subject as _subject
619
+ choice = _arrow_select(options, selected=0, title="",
620
+ collapse_to=_subject(tool_name, params),
621
+ shortcuts={"y": 0, "a": 1, "n": _deny_index}, numbered=True)
622
+
623
+ if choice == 0:
624
+ if tool_name == "run_command":
625
+ return ApprovalDecision.allow(policy=config_policy, user_approved=True)
626
+ return ApprovalDecision.allow()
627
+ if choice == 1:
628
+ return ApprovalDecision.allow(
629
+ policy=config_policy if tool_name == "run_command" else None,
630
+ user_approved=True,
631
+ command_prefix=_command_prefix,
632
+ tool_scope="" if _command_prefix else tool_name,
633
+ )
634
+ if choice == 2 and not _desktop:
635
+ if tool_name == "run_command":
636
+ return ApprovalDecision.allow(
637
+ policy=config_policy,
638
+ user_approved=True,
639
+ auto_approve_session=True,
640
+ )
641
+ return ApprovalDecision.allow(auto_approve_session=True)
642
+ return _deny_with_feedback(choice == _deny_index)
643
+ async def execute_aria_tool(base_url: str, tool_name: str, params: dict,
644
+ timeout: int = 30, auth_token: str = None,
645
+ max_retries: int = 2) -> dict:
646
+ """Execute an Aria tool via the backend API with auto-retry and TTL cache."""
647
+ # --- Parameter validation before sending to API ---
648
+ _symbol_tools = {
649
+ "get_market_data", "get_risk_metrics", "calculate_factors",
650
+ "get_alpha158_factors", "assess_portfolio_risk",
651
+ }
652
+ _date_tools = {"backtest_strategy", "stress_test_strategy"}
653
+
654
+ if tool_name in _symbol_tools and "symbol" in params:
655
+ sym = str(params["symbol"]).strip().upper()
656
+ if not re_module.match(r'^[A-Z0-9.\-/=]{1,12}$', sym):
657
+ return {"success": False, "error": f"Invalid symbol format: '{sym}'"}
658
+ params = {**params, "symbol": sym}
659
+
660
+ if tool_name in _date_tools:
661
+ for date_key in ("start_date", "end_date", "start", "end"):
662
+ if date_key in params:
663
+ date_val = str(params[date_key]).strip()
664
+ if not re_module.match(r'^\d{4}-\d{2}-\d{2}$', date_val):
665
+ return {"success": False, "error": f"Invalid date format for '{date_key}': '{date_val}' (expected YYYY-MM-DD)"}
666
+ # Check chronological order
667
+ start = params.get("start_date") or params.get("start")
668
+ end = params.get("end_date") or params.get("end")
669
+ if start and end and start > end:
670
+ return {"success": False, "error": f"start_date ({start}) must be before end_date ({end})"}
671
+
672
+ # Check cache for read-only tools
673
+ ttl = _CACHE_TTL.get(tool_name)
674
+ if ttl:
675
+ cache_key = f"{tool_name}:{json.dumps(params, sort_keys=True)}"
676
+ cached = _TOOL_CACHE.get(cache_key)
677
+ if cached and (time.time() - cached[1]) < ttl:
678
+ return cached[0]
679
+ import aiohttp
680
+ url = f"{base_url}/api/aria/execute-tool"
681
+ payload = {"tool_name": tool_name, "params": params}
682
+ headers = {}
683
+ if auth_token:
684
+ headers["Authorization"] = f"Bearer {auth_token}"
685
+ last_error = None
686
+ for attempt in range(max_retries + 1):
687
+ try:
688
+ async with aiohttp.ClientSession(trust_env=True) as session:
689
+ async with session.post(url, json=payload, headers=headers,
690
+ timeout=aiohttp.ClientTimeout(total=timeout)) as resp:
691
+ result = await resp.json()
692
+ if result.get("success") or attempt >= max_retries:
693
+ # Cache successful results for read-only tools
694
+ if result.get("success") and ttl:
695
+ _TOOL_CACHE[cache_key] = (result, time.time())
696
+ return result
697
+ last_error = result.get("error", "Unknown error")
698
+ except Exception as e:
699
+ last_error = str(e)
700
+ if attempt < max_retries:
701
+ await asyncio.sleep(1 * (attempt + 1)) # 1s, 2s backoff
702
+ return {"success": False, "error": f"Failed after {max_retries + 1} attempts: {last_error}"}
703
+ # Hard ceiling on one tool result fed back into the model, in characters.
704
+ # Individual branches below apply their own caps, but several produced
705
+ # unbounded text, and nothing capped the total: a turn could inject tens of
706
+ # thousands of characters of tool output into the context with no limit.
707
+ DEFAULT_TOOL_RESULT_CHAR_LIMIT = 4000
708
+
709
+ # Never truncate below this, or a "summary" degrades into an unusable stub.
710
+ MIN_TOOL_RESULT_CHAR_LIMIT = 400
711
+
712
+
713
+ def truncate_tool_summary(summary: str, limit: int = DEFAULT_TOOL_RESULT_CHAR_LIMIT) -> str:
714
+ """Clip a tool summary to *limit* characters, saying so explicitly.
715
+
716
+ The marker matters: silently dropping the tail makes a model treat a
717
+ partial result as complete. Head and tail are both kept because the useful
718
+ signal in command output and file content sits at the two ends — an error
719
+ traceback ends at the bottom, a file's structure shows at the top.
720
+ """
721
+ text = summary if isinstance(summary, str) else str(summary)
722
+ limit = max(int(limit or 0), MIN_TOOL_RESULT_CHAR_LIMIT)
723
+ if len(text) <= limit:
724
+ return text
725
+ marker_for = (
726
+ lambda n: f"\n\n… [{n} characters truncated to fit the context budget] …\n\n"
727
+ )
728
+ # The marker counts against the budget, so the returned text never exceeds
729
+ # *limit* — a cap that its own notice can overshoot is not a cap.
730
+ budget = max(limit - len(marker_for(len(text))), MIN_TOOL_RESULT_CHAR_LIMIT // 2)
731
+ head = int(budget * 0.7)
732
+ tail = budget - head
733
+ kept = text[:head] + (text[-tail:] if tail > 0 else "")
734
+ return text[:head] + marker_for(len(text) - len(kept)) + (text[-tail:] if tail > 0 else "")
735
+
736
+
737
+ def _format_tool_summary(
738
+ tool_name: str,
739
+ result: dict,
740
+ *,
741
+ char_limit: int = DEFAULT_TOOL_RESULT_CHAR_LIMIT,
742
+ ) -> str:
743
+ """Format a tool result into a concise, length-bounded follow-up summary."""
744
+ return truncate_tool_summary(_format_tool_summary_raw(tool_name, result), char_limit)
745
+
746
+
747
+ def _format_tool_summary_raw(tool_name: str, result: dict) -> str:
748
+ """Format tool result into a concise summary for AI follow-up context."""
749
+ if not result.get("success"):
750
+ return f"Error: {_g('_clean_tool_error_message')(result.get('error', 'failed'))}"
751
+ data = result.get("data", {})
752
+
753
+ # Local finance tools return their payload at the top level, whereas some
754
+ # remote adapters wrap it in ``data``. Treat both shapes consistently.
755
+ # Previously a successful local quote became ``{}`` here, leaving the LLM
756
+ # to invent a price/RSI despite the terminal having displayed real data.
757
+ tool_data = data if isinstance(data, dict) and data else result
758
+ if tool_name == "get_market_data":
759
+ symbol = tool_data.get("symbol", "")
760
+ price = tool_data.get("price", tool_data.get("latest_close"))
761
+ change = tool_data.get("change_pct")
762
+ provider = tool_data.get("provider", "unknown")
763
+ as_of = tool_data.get("as_of")
764
+ retrieved_at = tool_data.get("retrieved_at")
765
+ lines = [f"Market data for {symbol} (provider: {provider})"]
766
+ if price is not None:
767
+ currency = tool_data.get("currency", "")
768
+ lines.append(f"Price: {currency} {price}".strip())
769
+ if change is not None:
770
+ lines.append(f"Change: {change}%")
771
+ if as_of:
772
+ lines.append(f"Provider timestamp: {as_of}")
773
+ elif retrieved_at:
774
+ lines.append(f"Retrieved at: {retrieved_at} (provider did not supply trade timestamp)")
775
+ rsi = tool_data.get("rsi")
776
+ if rsi is not None:
777
+ try:
778
+ rsi_value = float(rsi)
779
+ rsi_label = "oversold" if rsi_value <= 30 else ("overbought" if rsi_value >= 70 else "neutral")
780
+ lines.append(f"RSI(14): {rsi_value:.2f} ({rsi_label})")
781
+ except (TypeError, ValueError):
782
+ lines.append("RSI(14): unavailable")
783
+ macd_hist = tool_data.get("macd_hist")
784
+ if macd_hist is not None:
785
+ lines.append(f"MACD histogram: {macd_hist}")
786
+ lines.append("Use only these values. Do not call this quote 'latest' beyond its stated timestamp.")
787
+ return "\n".join(lines)
788
+
789
+ if tool_name == "analyze_news":
790
+ news = tool_data.get("news") or []
791
+ provider = tool_data.get("provider", "unknown")
792
+ if not news:
793
+ return (
794
+ f"No relevant news returned (provider: {provider}). "
795
+ "Do not infer news sentiment; state that news evidence is unavailable."
796
+ )
797
+ lines = [f"News evidence (provider: {provider}, items: {len(news)}):"]
798
+ for item in news[:3]:
799
+ if not isinstance(item, dict):
800
+ continue
801
+ title = str(item.get("title") or "untitled")[:220]
802
+ published = str(item.get("time") or item.get("published_at") or "time unavailable")
803
+ publisher = str(item.get("publisher") or item.get("source") or "source unavailable")
804
+ lines.append(f"- {title} | {publisher} | {published}")
805
+ return "\n".join(lines)
806
+
807
+ if tool_name == "run_command":
808
+ exit_code = data.get("exit_code", -1)
809
+ stdout = data.get("stdout", "").strip()
810
+ stderr = data.get("stderr", "").strip()
811
+ out = f"exit_code={exit_code}"
812
+ if stdout:
813
+ out += f"\nstdout:\n{stdout[:2000]}"
814
+ if stderr and exit_code != 0:
815
+ out += f"\nstderr:\n{stderr[:500]}"
816
+ # Add actionable hints for common errors
817
+ if exit_code != 0:
818
+ combined = (stdout + " " + stderr).lower()
819
+ combined_raw = stdout + " " + stderr
820
+ if "can't open file" in combined or "no such file" in combined:
821
+ out += "\n\nHINT: The file does not exist. You must create it with write_file first, then run it."
822
+ elif "modulenotfounderror" in combined or "no module named" in combined:
823
+ # Extract module name
824
+ import re as _re
825
+ mod_match = _re.search(r"no module named ['\"]?(\w+)", combined)
826
+ mod_name = mod_match.group(1) if mod_match else "<module_name>"
827
+ out += f"\n\nHINT: Module '{mod_name}' is missing. Fix: run_command pip3 install {mod_name}, then run_command python3 to retry."
828
+ elif "nameerror" in combined:
829
+ # Extract the undefined name
830
+ import re as _re
831
+ name_match = _re.search(r"name ['\"](\w+)['\"] is not defined", combined_raw)
832
+ if name_match:
833
+ missing_name = name_match.group(1)
834
+ out += (f"\n\nHINT: '{missing_name}' is not defined — you forgot to import it. "
835
+ f"Use edit_file to add the missing import (e.g., 'import {missing_name}') at the top of the script, then retry.")
836
+ else:
837
+ out += "\n\nHINT: A variable or module is not defined. Use read_file to check imports, edit_file to add the missing import, then retry."
838
+ elif "syntaxerror" in combined:
839
+ import re as _re
840
+ line_match = _re.search(r"line (\d+)", combined)
841
+ line_hint = f" at line {line_match.group(1)}" if line_match else ""
842
+ out += f"\n\nHINT: Syntax error{line_hint}. Use read_file to see the code, then edit_file to fix the exact line, then retry."
843
+ elif "typeerror" in combined:
844
+ out += "\n\nHINT: Type error — wrong argument types or wrong number of arguments. Use read_file to inspect, edit_file to fix, then retry."
845
+ elif "keyerror" in combined or "indexerror" in combined:
846
+ # Special hint for yfinance MultiIndex KeyError
847
+ if any(col in combined_raw for col in ("'Close'", "'Open'", "'High'", "'Low'", "'Volume'")):
848
+ out += ("\n\nHINT: yfinance MultiIndex KeyError — yf.download() returns MultiIndex columns "
849
+ "when downloading multiple tickers. Fix: add `if isinstance(df.columns, pd.MultiIndex): "
850
+ "df.columns = df.columns.droplevel(1)` right after yf.download(). "
851
+ "Use edit_file to add this fix, then retry.")
852
+ else:
853
+ out += "\n\nHINT: Data structure mismatch. Use read_file to check the code logic. The data may have different column names or fewer elements than expected."
854
+ elif "attributeerror" in combined:
855
+ out += "\n\nHINT: Attribute error — the object doesn't have that method/property. Check the library version or API docs. Use read_file then edit_file to fix."
856
+ elif "valueerror" in combined:
857
+ out += "\n\nHINT: Value error — invalid value passed to a function. Use read_file to check the data types and fix with edit_file."
858
+ elif "permission denied" in combined:
859
+ out += "\n\nHINT: Permission denied. Try adding chmod +x, or run with python3 explicitly."
860
+ else:
861
+ out += "\n\nHINT: Script failed. Use read_file to inspect the code, find the error, edit_file to fix it, then run_command to retry. Do NOT give up."
862
+ else:
863
+ # Script succeeded — auto-verify and auto-open output files.
864
+ from aria_code.artifacts import user_generated_dir as _aria_user_generated_dir
865
+ output_dir = _aria_user_generated_dir()
866
+ try:
867
+ recent_files = []
868
+ for ext in ("*.png", "*.html", "*.csv", "*.pdf", "*.xlsx"):
869
+ for f in output_dir.rglob(ext):
870
+ if (time.time() - f.stat().st_mtime) < 30:
871
+ recent_files.append(f)
872
+ # Also detect files mentioned in stdout (e.g., "Saved to /path/to/file.png")
873
+ saved_pattern = re_module.findall(r'(?:saved?\s+(?:to|as|at)|wrote|output|created)[:\s]+([^\s\'"]+\.(?:png|html|csv|pdf))', stdout, re_module.IGNORECASE)
874
+ for sp in saved_pattern:
875
+ p = pathlib.Path(sp).expanduser().resolve()
876
+ if p.exists() and p not in recent_files:
877
+ recent_files.append(p)
878
+ if recent_files:
879
+ names = [f.name for f in recent_files]
880
+ out += f"\n\nVerified: output files created: {', '.join(names)}"
881
+ # Auto-open on macOS (non-blocking)
882
+ for f in recent_files[:3]:
883
+ try:
884
+ subprocess.Popen(["open", str(f)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
885
+ except Exception:
886
+ pass
887
+ if _g("HAS_RICH"):
888
+ _g("console").print(f" [dim]Opened {', '.join(names[:3])}[/dim]")
889
+ else:
890
+ combined_check = (stdout + " " + stderr).lower()
891
+ if any(kw in combined_check for kw in ("chart", "plot", "figure", "savefig", "save")):
892
+ out += ("\n\nWARNING: Script ran but no output files detected in the Aria generated output directory. "
893
+ "Check the save path uses ~/Documents/Aria Code/generated.")
894
+ except Exception:
895
+ pass
896
+ return out
897
+ if tool_name == "write_file":
898
+ _base = f"OK: {data.get('action', 'created')} {data.get('path', '')} ({data.get('lines', 0)} lines)"
899
+ if result.get("warning"):
900
+ _base += f"\n\n{result['warning']}"
901
+ return _base
902
+ if tool_name == "edit_file":
903
+ _base = f"OK: edited {data.get('path', '')} ({data.get('replacements', 0)} replacements)"
904
+ if result.get("warning"):
905
+ _base += f"\n\n{result['warning']}"
906
+ return _base
907
+ if tool_name == "multi_edit":
908
+ _base = f"OK: {data.get('edits_applied', 0)} edits applied to {data.get('path', '')}"
909
+ if result.get("warning"):
910
+ _base += f"\n\n{result['warning']}"
911
+ return _base
912
+ if tool_name == "update_todos":
913
+ _todos = data.get("todos", [])
914
+ _lines = [f"任务进度 {data.get('completed', 0)}/{data.get('total', len(_todos))}:"]
915
+ _mark = {"completed": "[x]", "in_progress": "[>]", "pending": "[ ]"}
916
+ for _t in _todos:
917
+ _lines.append(f" {_mark.get(_t.get('status'), '[ ]')} {_t.get('content', '')}")
918
+ return "\n".join(_lines)
919
+ if tool_name == "read_file":
920
+ content = data.get("content", "")
921
+ return f"OK: {data.get('lines', 0)} lines\n{content[:2000]}"
922
+ if tool_name == "list_files":
923
+ items = data.get("items", [])
924
+ names = [it["name"] if isinstance(it, dict) else str(it) for it in items[:20]]
925
+ return f"OK: {data.get('count', len(items))} items: {', '.join(names)}"
926
+ if tool_name == "search_code":
927
+ matches = data.get("matches", [])
928
+ return f"OK: {len(matches)} matches\n" + "\n".join(str(m)[:200] for m in matches[:10])
929
+ # Remote tools — JSON summary
930
+ return json.dumps(data, ensure_ascii=False)[:2000]
931
+
932
+ __all__ = ['_g', '_tool_analyze_file', '_tool_read_file', '_strip_markdown_fences', '_auto_fix_python', '_write_policy_confirm', '_tool_write_file', '_tool_edit_file', '_tool_multi_edit', '_tool_update_todos', '_tool_list_files', '_tool_search_code', '_tool_run_command', '_tool_web_fetch', '_tool_github', '_tool_glob', '_tool_notebook_read', '_tool_notebook_edit', '_tool_broker_query', '_tool_broker_order', '_tool_get_market_data', '_tool_get_market_history', '_todo_schema', '_wrap_bare_schemas', '_dedup_tool_schemas', '_show_edit_preview', '_show_multi_edit_preview', '_show_write_preview', '_apply_tool_approval', '_DENY_LABEL', '_deny_help', '_deny_with_feedback', '_assess_for_approval', '_show_risk_card', '_note_auto_approval', '_confirm_tool_execution_decision', 'execute_aria_tool', '_format_tool_summary', '_format_tool_summary_raw', 'truncate_tool_summary', 'DEFAULT_TOOL_RESULT_CHAR_LIMIT', 'MIN_TOOL_RESULT_CHAR_LIMIT']