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,219 @@
1
+ """Safe workspace file operations for Aria Code."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pathlib
6
+ import re
7
+ import tempfile
8
+ import os
9
+ from dataclasses import dataclass
10
+ from typing import Any, Dict, List
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class ReadResult:
15
+ path: str
16
+ lines: int
17
+ content: str
18
+
19
+
20
+ class WorkspaceSecurity:
21
+ """Path security policy shared by CLI tools and future runtimes."""
22
+
23
+ BLOCKED_ROOTS = (
24
+ "/etc",
25
+ "/sys",
26
+ "/proc",
27
+ "/dev",
28
+ "/boot",
29
+ "/root",
30
+ )
31
+
32
+ def __init__(
33
+ self,
34
+ cwd: str | pathlib.Path | None = None,
35
+ *,
36
+ allowed_roots: List[str | pathlib.Path] | None = None,
37
+ write_roots: List[str | pathlib.Path] | None = None,
38
+ allow_home: bool | None = None,
39
+ ) -> None:
40
+ self.cwd = pathlib.Path(cwd or pathlib.Path.cwd()).expanduser().resolve()
41
+ self._configured_roots = tuple(
42
+ pathlib.Path(root).expanduser().resolve() for root in (allowed_roots or ())
43
+ )
44
+ self._write_roots = tuple(
45
+ pathlib.Path(root).expanduser().resolve() for root in (write_roots or ())
46
+ )
47
+ if allow_home is None:
48
+ # Local CLI keeps its historical convenience. Remote/API workers must
49
+ # set ARIA_RUNTIME_SCOPE=remote (or pass allow_home=False) so one user
50
+ # can never inspect the worker account's home directory.
51
+ allow_home = os.getenv("ARIA_RUNTIME_SCOPE", "local").strip().lower() != "remote"
52
+ self.allow_home = bool(allow_home)
53
+
54
+ def allowed_roots(self) -> List[pathlib.Path]:
55
+ roots = [self.cwd, *self._configured_roots, *self._write_roots]
56
+ if self.allow_home:
57
+ roots.append(pathlib.Path.home().resolve())
58
+ # Temporary roots are required by the local CLI, but a remote worker is
59
+ # confined to its explicitly assigned workspace root.
60
+ if not self.allow_home:
61
+ return list(dict.fromkeys(roots))
62
+ roots.append(pathlib.Path("/tmp").resolve())
63
+ for tmp_candidate in (
64
+ "/var/folders",
65
+ "/private/tmp",
66
+ "/private/var/folders",
67
+ "/var/tmp",
68
+ "/private/var/tmp",
69
+ tempfile.gettempdir(),
70
+ ):
71
+ try:
72
+ root = pathlib.Path(tmp_candidate).resolve()
73
+ if root not in roots:
74
+ roots.append(root)
75
+ except Exception:
76
+ pass
77
+ return roots
78
+
79
+ @classmethod
80
+ def from_tool_params(cls, params: dict):
81
+ unrestricted = params.get("_permission_mode") == "full-access" and not params.get("_workspace_restricted")
82
+ return cls(
83
+ cwd=params.get("_workspace") or os.getcwd(),
84
+ allowed_roots=["/"] if unrestricted else params.get("_allowed_read_roots", ()),
85
+ write_roots=["/"] if unrestricted else params.get("_allowed_write_roots", ()),
86
+ allow_home=False if params.get("_workspace_restricted") else None,
87
+ )
88
+
89
+ def writable_roots(self) -> List[pathlib.Path]:
90
+ roots = [self.cwd, *self._write_roots]
91
+ if self.allow_home:
92
+ roots += [pathlib.Path("/tmp").resolve(), pathlib.Path(tempfile.gettempdir()).resolve()]
93
+ try:
94
+ from aria_code.artifacts import artifact_root, user_output_root
95
+ roots += [pathlib.Path(artifact_root()).resolve(), pathlib.Path(user_output_root()).resolve()]
96
+ except ImportError:
97
+ pass
98
+ return list(dict.fromkeys(roots))
99
+
100
+ def resolve(self, path: str | pathlib.Path) -> pathlib.Path:
101
+ target = pathlib.Path(path).expanduser()
102
+ return (target if target.is_absolute() else self.cwd / target).resolve()
103
+
104
+ def is_safe_path(self, path: str | pathlib.Path, *, write: bool = False) -> bool:
105
+ resolved = self.resolve(path)
106
+ for blocked in self.BLOCKED_ROOTS:
107
+ try:
108
+ resolved.relative_to(pathlib.Path(blocked).resolve())
109
+ return False
110
+ except ValueError:
111
+ pass
112
+ for root in self.writable_roots() if write else self.allowed_roots():
113
+ try:
114
+ resolved.relative_to(root)
115
+ return True
116
+ except ValueError:
117
+ pass
118
+ return False
119
+
120
+ def require_safe(self, path: str | pathlib.Path, *, write: bool = False) -> pathlib.Path:
121
+ resolved = self.resolve(path)
122
+ if not self.is_safe_path(resolved, write=write):
123
+ raise PermissionError(f"Access denied: path '{resolved}' is outside allowed directories")
124
+ return resolved
125
+
126
+
127
+ class WorkspaceFiles:
128
+ """Read, list, and search files under Aria Code's safety policy."""
129
+
130
+ MAX_READ_BYTES = 2_000_000
131
+ LARGE_FILE_BYTES = 500_000
132
+ LARGE_FILE_DEFAULT_LINES = 500
133
+ MAX_SEARCH_BYTES = 5_000_000
134
+
135
+ def __init__(self, security: WorkspaceSecurity | None = None) -> None:
136
+ self.security = security or WorkspaceSecurity()
137
+
138
+ def read_file(self, path: str, offset: int = 0, limit: int = 0) -> ReadResult:
139
+ target = self.security.require_safe(path)
140
+ if not target.exists():
141
+ raise FileNotFoundError(f"File not found: {target}")
142
+ if not target.is_file():
143
+ raise ValueError(f"Not a file: {target}")
144
+ size = target.stat().st_size
145
+ if size > self.MAX_READ_BYTES:
146
+ raise ValueError(
147
+ f"File too large: {size:,} bytes (max 2 MB). "
148
+ "Use offset/limit parameters to read sections."
149
+ )
150
+ text = target.read_text(encoding="utf-8", errors="replace")
151
+ lines = text.splitlines()
152
+ total_lines = len(lines)
153
+ if not offset and not limit and size > self.LARGE_FILE_BYTES:
154
+ limit = self.LARGE_FILE_DEFAULT_LINES
155
+ if offset or limit:
156
+ end = offset + limit if limit else total_lines
157
+ shown = lines[offset:end]
158
+ content = "\n".join(f"{i + offset + 1:4d}│ {line}" for i, line in enumerate(shown))
159
+ if limit and end < total_lines:
160
+ content += f"\n... [{len(shown)} of {total_lines} lines shown — use offset/limit to read more]"
161
+ return ReadResult(str(target), len(shown), content[:30000])
162
+ content = "\n".join(f"{i + 1:4d}│ {line}" for i, line in enumerate(lines))
163
+ return ReadResult(str(target), len(lines), content[:30000])
164
+
165
+ def list_files(self, path: str = ".", pattern: str = "*") -> Dict[str, Any]:
166
+ target = self.security.require_safe(path)
167
+ if not target.exists():
168
+ raise FileNotFoundError(f"Path not found: {target}")
169
+ if target.is_file():
170
+ read = self.read_file(str(target))
171
+ return {
172
+ "path": read.path,
173
+ "pattern": pattern,
174
+ "count": 1,
175
+ "items": [{"name": target.name, "type": "file", "size": target.stat().st_size}],
176
+ "content": read.content,
177
+ }
178
+ matches = sorted(target.glob(pattern))[:100]
179
+ items = []
180
+ for match in matches:
181
+ try:
182
+ self.security.require_safe(match)
183
+ except PermissionError:
184
+ continue
185
+ rel = match.relative_to(target) if match.is_relative_to(target) else match
186
+ kind = "dir" if match.is_dir() else "file"
187
+ size = match.stat().st_size if match.is_file() else 0
188
+ items.append({"name": str(rel), "type": kind, "size": size})
189
+ return {"path": str(target), "pattern": pattern, "count": len(items), "items": items}
190
+
191
+ def search_code(self, pattern: str, path: str = ".", file_glob: str = "**/*.py") -> Dict[str, Any]:
192
+ if not pattern:
193
+ raise ValueError("Missing 'pattern' parameter")
194
+ target = self.security.require_safe(path)
195
+ regex = re.compile(pattern, re.IGNORECASE)
196
+ matches = []
197
+ for file_path in sorted(target.glob(file_glob))[:200]:
198
+ try:
199
+ safe_file = self.security.require_safe(file_path)
200
+ except PermissionError:
201
+ continue
202
+ if not safe_file.is_file() or safe_file.stat().st_size > self.MAX_SEARCH_BYTES:
203
+ continue
204
+ try:
205
+ lines = safe_file.read_text(encoding="utf-8", errors="replace").splitlines()
206
+ except Exception:
207
+ continue
208
+ for line_number, line in enumerate(lines, 1):
209
+ if regex.search(line):
210
+ matches.append({
211
+ "file": str(safe_file.relative_to(target) if safe_file.is_relative_to(target) else safe_file),
212
+ "line": line_number,
213
+ "content": line.strip()[:200],
214
+ })
215
+ if len(matches) >= 50:
216
+ break
217
+ if len(matches) >= 50:
218
+ break
219
+ return {"pattern": pattern, "path": str(target), "count": len(matches), "matches": matches}
@@ -0,0 +1,167 @@
1
+ """Verification planning for Aria Code workspaces."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ import json
7
+ import os
8
+ import pathlib
9
+ import shlex
10
+ import subprocess
11
+ from dataclasses import dataclass
12
+ from typing import Iterable, List
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class VerificationPlan:
17
+ commands: List[str]
18
+ reason: str
19
+
20
+
21
+ class VerificationPlanner:
22
+ """Infer focused verification commands from changed files and project files."""
23
+
24
+ def __init__(self, root: str | pathlib.Path = ".") -> None:
25
+ self.root = pathlib.Path(root).expanduser().resolve()
26
+
27
+ def infer(self, paths: Iterable[str] | None = None) -> VerificationPlan:
28
+ changed = [p for p in (paths or []) if p]
29
+ if not changed:
30
+ changed = self._git_changed_files()
31
+ commands: List[str] = []
32
+ reasons: List[str] = []
33
+
34
+ py_files = [p for p in changed if p.endswith(".py")]
35
+ if py_files:
36
+ quoted = " ".join(shlex.quote(p) for p in py_files[:20])
37
+ commands.append(f"python3 -m py_compile {quoted}")
38
+ reasons.append("Python files changed")
39
+ if (self._has_any("pytest.ini", "pyproject.toml", "setup.cfg", "tox.ini")
40
+ or (self.root / "tests").exists() or self._has_python_tests()):
41
+ commands.append(self._python_test_command())
42
+
43
+ frontend_files = [
44
+ p for p in changed
45
+ if p.endswith((".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".css"))
46
+ ]
47
+ if frontend_files and (self.root / "package.json").exists():
48
+ scripts = self._package_scripts()
49
+ reasons.append("Node/TypeScript files changed")
50
+ if "test" in scripts:
51
+ commands.append("npm test")
52
+ if "build" in scripts:
53
+ commands.append("npm run build")
54
+ elif (self.root / "tsconfig.json").exists():
55
+ commands.append("npx tsc --noEmit")
56
+
57
+ if not commands:
58
+ if (self.root / "pyproject.toml").exists() or (self.root / "tests").exists():
59
+ commands.append(self._python_test_command())
60
+ reasons.append("Python project detected")
61
+ elif (self.root / "package.json").exists():
62
+ scripts = self._package_scripts()
63
+ if "test" in scripts:
64
+ commands.append("npm test")
65
+ reasons.append("Node project detected")
66
+ elif "build" in scripts:
67
+ commands.append("npm run build")
68
+ reasons.append("Node project detected")
69
+
70
+ deduped = list(dict.fromkeys(commands))
71
+ reason = "; ".join(dict.fromkeys(reasons)) if reasons else "No focused check inferred"
72
+ return VerificationPlan(deduped, reason)
73
+
74
+ def _has_python_tests(self) -> bool:
75
+ """Test files beside the code count as a test suite.
76
+
77
+ Small projects and scripts keep `test_x.py` next to `x.py`, with no config
78
+ file and no `tests/` directory. Without this they were "verified" by
79
+ `py_compile` alone, which only says the syntax is valid; the acceptance
80
+ gate then reported a fix as verified without ever running its tests.
81
+ """
82
+ skip = {".git", "node_modules", ".venv", "venv", "env", "__pycache__", "build", "dist", "site-packages"}
83
+ for current, dirs, files in os.walk(self.root):
84
+ depth = len(pathlib.Path(current).relative_to(self.root).parts)
85
+ dirs[:] = [d for d in dirs if d not in skip and not d.startswith(".")] if depth < 2 else []
86
+ if any(f.startswith("test_") and f.endswith(".py") or f.endswith("_test.py") or f == "conftest.py"
87
+ for f in files):
88
+ return True
89
+ return False
90
+
91
+ def _has_any(self, *names: str) -> bool:
92
+ return any((self.root / name).exists() for name in names)
93
+
94
+ def _python_test_command(self) -> str:
95
+ """Choose the project's runner without importing its test code.
96
+
97
+ A tests directory does not imply pytest. For a stdlib unittest project,
98
+ forcing pytest makes the agent install an unnecessary dependency just
99
+ to satisfy the acceptance gate.
100
+ """
101
+ for path in (self.root / "pytest.ini", self.root / "pyproject.toml", self.root / "setup.cfg"):
102
+ if path.is_file():
103
+ text = path.read_text(encoding="utf-8", errors="replace")
104
+ if path.name == "pytest.ini" or "[tool.pytest" in text or "[tool:pytest]" in text:
105
+ return "python3 -m pytest -q"
106
+ tests_root = self.root / "tests" if (self.root / "tests").is_dir() else self.root
107
+ candidates = (list(tests_root.glob("**/test*.py"))[:100] if tests_root != self.root
108
+ else (list(self.root.glob("test*.py")) + list(self.root.glob("*_test.py")))[:100])
109
+ uses_unittest = False
110
+ for path in candidates:
111
+ try:
112
+ tree = ast.parse(path.read_text(encoding="utf-8"))
113
+ except (OSError, SyntaxError, UnicodeError):
114
+ return "python3 -m pytest -q"
115
+ for node in ast.walk(tree):
116
+ modules = ([alias.name.split(".")[0] for alias in node.names] if isinstance(node, ast.Import)
117
+ else [str(node.module or "").split(".")[0]] if isinstance(node, ast.ImportFrom) else [])
118
+ if "pytest" in modules:
119
+ return "python3 -m pytest -q"
120
+ uses_unittest |= "unittest" in modules
121
+ if any(isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name.startswith("test") for node in tree.body):
122
+ return "python3 -m pytest -q"
123
+ if uses_unittest:
124
+ directory = "tests" if tests_root != self.root else "."
125
+ return f"python3 -m unittest discover -s {directory} -v"
126
+ return "python3 -m pytest -q"
127
+
128
+ def _package_scripts(self) -> dict:
129
+ try:
130
+ data = json.loads((self.root / "package.json").read_text(encoding="utf-8"))
131
+ return data.get("scripts", {}) if isinstance(data.get("scripts", {}), dict) else {}
132
+ except Exception:
133
+ return {}
134
+
135
+ def _git_changed_files(self) -> List[str]:
136
+ try:
137
+ proc = subprocess.run(
138
+ ["git", "diff", "--name-only", "HEAD"],
139
+ cwd=self.root,
140
+ capture_output=True,
141
+ text=True,
142
+ timeout=5,
143
+ check=False,
144
+ )
145
+ if proc.returncode == 0 and proc.stdout.strip():
146
+ return [line.strip() for line in proc.stdout.splitlines() if line.strip()]
147
+ except Exception:
148
+ pass
149
+ try:
150
+ proc = subprocess.run(
151
+ ["git", "status", "--short"],
152
+ cwd=self.root,
153
+ capture_output=True,
154
+ text=True,
155
+ timeout=5,
156
+ check=False,
157
+ )
158
+ if proc.returncode != 0:
159
+ return []
160
+ files = []
161
+ for line in proc.stdout.splitlines():
162
+ if not line.strip():
163
+ continue
164
+ files.append(line[3:].strip())
165
+ return files
166
+ except Exception:
167
+ return []
@@ -0,0 +1,199 @@
1
+ """Approval-ready text changes for isolated Aria Code workspaces."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import difflib
6
+ import hashlib
7
+ import json
8
+ import os
9
+ import secrets
10
+ import tempfile
11
+ from dataclasses import asdict, dataclass
12
+ from datetime import datetime, timezone
13
+ from pathlib import Path, PurePosixPath
14
+ from typing import Any
15
+
16
+ from aria_code.project_review import _is_sensitive
17
+ from aria_code.workspace_service import WorkspaceService, WorkspaceServiceError
18
+
19
+
20
+ MAX_CHANGE_BYTES = 1 * 1024 * 1024
21
+ MAX_DIFF_CHARS = 120_000
22
+
23
+
24
+ class WorkspaceChangeError(ValueError):
25
+ pass
26
+
27
+
28
+ def _now() -> str:
29
+ return datetime.now(timezone.utc).isoformat()
30
+
31
+
32
+ def _digest(data: bytes) -> str:
33
+ return hashlib.sha256(data).hexdigest()
34
+
35
+
36
+ @dataclass(frozen=True)
37
+ class WorkspaceChange:
38
+ schema_version: str
39
+ change_id: str
40
+ workspace_id: str
41
+ path: str
42
+ operation: str
43
+ status: str
44
+ base_sha256: str
45
+ proposed_sha256: str
46
+ created_at: str
47
+ updated_at: str
48
+ diff: str
49
+
50
+ def public_dict(self) -> dict[str, Any]:
51
+ return asdict(self)
52
+
53
+
54
+ class WorkspaceChangeService:
55
+ """Stages text changes outside the source tree until approval is verified."""
56
+
57
+ def __init__(self, workspaces: WorkspaceService) -> None:
58
+ self.workspaces = workspaces
59
+
60
+ def _change_root(self, workspace_id: str) -> Path:
61
+ root = self.workspaces._workspace_dir(workspace_id) / ".aria-changes"
62
+ root.mkdir(mode=0o700, exist_ok=True)
63
+ return root
64
+
65
+ def _directory(self, workspace_id: str, change_id: str) -> Path:
66
+ if not change_id.startswith("chg_") or len(change_id) != 28:
67
+ raise WorkspaceChangeError("Invalid change id.")
68
+ root = self._change_root(workspace_id).resolve()
69
+ directory = (root / change_id).resolve()
70
+ if directory.parent != root:
71
+ raise WorkspaceChangeError("Invalid change path.")
72
+ return directory
73
+
74
+ @staticmethod
75
+ def _metadata(directory: Path) -> Path:
76
+ return directory / "change.json"
77
+
78
+ def propose(
79
+ self,
80
+ workspace_id: str,
81
+ *,
82
+ owner_id: str,
83
+ path: str,
84
+ content: str,
85
+ expected_sha256: str | None = None,
86
+ ) -> WorkspaceChange:
87
+ relative = PurePosixPath(str(path or "").replace("\\", "/"))
88
+ if not relative.parts or relative.is_absolute() or ".." in relative.parts:
89
+ raise WorkspaceChangeError("Change path must be relative to the workspace.")
90
+ if _is_sensitive(relative):
91
+ raise WorkspaceChangeError("Sensitive configuration and key files cannot be written.")
92
+ proposed = content.encode("utf-8")
93
+ if len(proposed) > MAX_CHANGE_BYTES:
94
+ raise WorkspaceChangeError("Proposed file exceeds the 1 MB text-change limit.")
95
+ try:
96
+ target = self.workspaces.source_path(workspace_id, str(relative), owner_id=owner_id)
97
+ except WorkspaceServiceError as exc:
98
+ raise WorkspaceChangeError(str(exc)) from exc
99
+ if target.exists() and not target.is_file():
100
+ raise WorkspaceChangeError("Change target is not a regular file.")
101
+ try:
102
+ before = target.read_bytes() if target.exists() else b""
103
+ before_text = before.decode("utf-8")
104
+ except (OSError, UnicodeDecodeError) as exc:
105
+ raise WorkspaceChangeError("Only UTF-8 text files can be changed.") from exc
106
+ base_hash = _digest(before)
107
+ if expected_sha256 and not secrets.compare_digest(expected_sha256, base_hash):
108
+ raise WorkspaceChangeError("File changed since it was reviewed; refresh before proposing a patch.")
109
+ if before == proposed:
110
+ raise WorkspaceChangeError("Proposed content is identical to the current file.")
111
+ diff = "".join(difflib.unified_diff(
112
+ before_text.splitlines(keepends=True),
113
+ content.splitlines(keepends=True),
114
+ fromfile=f"a/{relative}",
115
+ tofile=f"b/{relative}",
116
+ ))
117
+ if len(diff) > MAX_DIFF_CHARS:
118
+ diff = diff[:MAX_DIFF_CHARS] + "\n... diff truncated ...\n"
119
+ change_id = f"chg_{secrets.token_hex(12)}"
120
+ directory = self._directory(workspace_id, change_id)
121
+ directory.mkdir(mode=0o700)
122
+ (directory / "before.bin").write_bytes(before)
123
+ (directory / "proposed.txt").write_bytes(proposed)
124
+ created = _now()
125
+ change = WorkspaceChange(
126
+ schema_version="aria.workspace-change.v1",
127
+ change_id=change_id,
128
+ workspace_id=workspace_id,
129
+ path=str(relative),
130
+ operation="update" if target.exists() else "create",
131
+ status="proposed",
132
+ base_sha256=base_hash,
133
+ proposed_sha256=_digest(proposed),
134
+ created_at=created,
135
+ updated_at=created,
136
+ diff=diff,
137
+ )
138
+ self._metadata(directory).write_text(json.dumps(asdict(change), indent=2), encoding="utf-8")
139
+ return change
140
+
141
+ def get(self, workspace_id: str, change_id: str, *, owner_id: str) -> WorkspaceChange:
142
+ self.workspaces.get(workspace_id, owner_id=owner_id)
143
+ try:
144
+ raw = json.loads(self._metadata(self._directory(workspace_id, change_id)).read_text(encoding="utf-8"))
145
+ return WorkspaceChange(**raw)
146
+ except (OSError, TypeError, ValueError, json.JSONDecodeError) as exc:
147
+ raise WorkspaceChangeError("Change proposal not found.") from exc
148
+
149
+ def _store(self, change: WorkspaceChange) -> None:
150
+ directory = self._directory(change.workspace_id, change.change_id)
151
+ self._metadata(directory).write_text(json.dumps(asdict(change), indent=2), encoding="utf-8")
152
+
153
+ def apply(self, workspace_id: str, change_id: str, *, owner_id: str) -> WorkspaceChange:
154
+ change = self.get(workspace_id, change_id, owner_id=owner_id)
155
+ if change.status != "proposed":
156
+ raise WorkspaceChangeError("Only a proposed change can be applied.")
157
+ target = self.workspaces.source_path(workspace_id, change.path, owner_id=owner_id)
158
+ current = target.read_bytes() if target.exists() else b""
159
+ if not secrets.compare_digest(_digest(current), change.base_sha256):
160
+ raise WorkspaceChangeError("File changed after the proposal; approval is stale.")
161
+ proposed = (self._directory(workspace_id, change_id) / "proposed.txt").read_bytes()
162
+ if not secrets.compare_digest(_digest(proposed), change.proposed_sha256):
163
+ raise WorkspaceChangeError("Stored proposal failed its integrity check.")
164
+ target.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
165
+ fd, temporary = tempfile.mkstemp(prefix=f".{target.name}.", dir=target.parent)
166
+ try:
167
+ with os.fdopen(fd, "wb") as stream:
168
+ stream.write(proposed)
169
+ stream.flush()
170
+ os.fsync(stream.fileno())
171
+ os.chmod(temporary, 0o600)
172
+ os.replace(temporary, target)
173
+ finally:
174
+ if os.path.exists(temporary):
175
+ os.unlink(temporary)
176
+ updated = WorkspaceChange(**{**asdict(change), "status": "applied", "updated_at": _now()})
177
+ self._store(updated)
178
+ return updated
179
+
180
+ def rollback(self, workspace_id: str, change_id: str, *, owner_id: str) -> WorkspaceChange:
181
+ change = self.get(workspace_id, change_id, owner_id=owner_id)
182
+ if change.status != "applied":
183
+ raise WorkspaceChangeError("Only an applied change can be rolled back.")
184
+ target = self.workspaces.source_path(workspace_id, change.path, owner_id=owner_id)
185
+ current = target.read_bytes() if target.exists() else b""
186
+ if not secrets.compare_digest(_digest(current), change.proposed_sha256):
187
+ raise WorkspaceChangeError("File changed after apply; automatic rollback would overwrite newer work.")
188
+ before = (self._directory(workspace_id, change_id) / "before.bin").read_bytes()
189
+ if change.operation == "create":
190
+ target.unlink(missing_ok=True)
191
+ else:
192
+ target.write_bytes(before)
193
+ try:
194
+ target.chmod(0o600)
195
+ except OSError:
196
+ pass
197
+ updated = WorkspaceChange(**{**asdict(change), "status": "rolled_back", "updated_at": _now()})
198
+ self._store(updated)
199
+ return updated