aria-code 0.52.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (478) hide show
  1. aria_code/__init__.py +42 -0
  2. aria_code/_version.py +20 -0
  3. aria_code/adk_apps/__init__.py +1 -0
  4. aria_code/adk_apps/aria_code_review/__init__.py +1 -0
  5. aria_code/adk_apps/aria_code_review/agent.py +32 -0
  6. aria_code/adk_apps/aria_research/__init__.py +1 -0
  7. aria_code/adk_apps/aria_research/agent.py +31 -0
  8. aria_code/agents/__init__.py +34 -0
  9. aria_code/agents/base.py +318 -0
  10. aria_code/agents/code_review.py +200 -0
  11. aria_code/agents/deep/__init__.py +37 -0
  12. aria_code/agents/deep/calibration_loop.py +144 -0
  13. aria_code/agents/deep/critic.py +125 -0
  14. aria_code/agents/deep/deepen.py +193 -0
  15. aria_code/agents/deep/models.py +149 -0
  16. aria_code/agents/deep/pipeline.py +164 -0
  17. aria_code/agents/deep/quant_fusion.py +192 -0
  18. aria_code/agents/deep/themes.py +95 -0
  19. aria_code/agents/deep/tiers.py +106 -0
  20. aria_code/agents/engineering/__init__.py +14 -0
  21. aria_code/agents/engineering/coder.py +222 -0
  22. aria_code/agents/engineering/tester.py +145 -0
  23. aria_code/agents/financial/__init__.py +21 -0
  24. aria_code/agents/financial/cashflow_burn.py +119 -0
  25. aria_code/agents/financial/catalyst.py +279 -0
  26. aria_code/agents/financial/corporate_finance.py +175 -0
  27. aria_code/agents/financial/debate.py +224 -0
  28. aria_code/agents/financial/earnings.py +303 -0
  29. aria_code/agents/financial/fundamental.py +191 -0
  30. aria_code/agents/financial/macro.py +99 -0
  31. aria_code/agents/financial/news.py +207 -0
  32. aria_code/agents/financial/risk.py +165 -0
  33. aria_code/agents/financial/sector.py +279 -0
  34. aria_code/agents/financial/strategist.py +121 -0
  35. aria_code/agents/financial/stripe_revenue.py +127 -0
  36. aria_code/agents/financial/synthesis.py +274 -0
  37. aria_code/agents/financial/technical.py +258 -0
  38. aria_code/agents/orchestrator.py +44 -0
  39. aria_code/agents/portfolio_agent.py +363 -0
  40. aria_code/agents/realty/__init__.py +62 -0
  41. aria_code/agents/realty/asset_diagnosis.py +152 -0
  42. aria_code/agents/realty/business_match.py +166 -0
  43. aria_code/agents/realty/cashflow_verify.py +209 -0
  44. aria_code/agents/realty/contract_rules.py +210 -0
  45. aria_code/agents/realty/energy_anomaly.py +189 -0
  46. aria_code/agents/realty/exit_settlement.py +208 -0
  47. aria_code/agents/realty/fulfillment_risk.py +206 -0
  48. aria_code/agents/realty/ops_optimize.py +160 -0
  49. aria_code/agents/realty/revenue_share.py +215 -0
  50. aria_code/agents/registry.py +158 -0
  51. aria_code/agents/signal_scheme.py +111 -0
  52. aria_code/agents/sports/__init__.py +0 -0
  53. aria_code/agents/sports/football_agent.py +197 -0
  54. aria_code/agents/supervisor.py +113 -0
  55. aria_code/agents/team.py +492 -0
  56. aria_code/agents/warehouse/__init__.py +25 -0
  57. aria_code/agents/warehouse/contracts.py +26 -0
  58. aria_code/agents/warehouse/dashboard.py +155 -0
  59. aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
  60. aria_code/agents/warehouse/inbound_exceptions.py +25 -0
  61. aria_code/agents/warehouse/inventory_health.py +24 -0
  62. aria_code/agents/warehouse/local_run.py +71 -0
  63. aria_code/agents/warehouse/local_snapshot.py +132 -0
  64. aria_code/agents/warehouse/logistics_cost.py +84 -0
  65. aria_code/agents/warehouse/logistics_sync.py +21 -0
  66. aria_code/agents/warehouse/workflow.py +31 -0
  67. aria_code/aliyun_data_client.py +4 -0
  68. aria_code/apps/README.md +12 -0
  69. aria_code/apps/__init__.py +2 -0
  70. aria_code/apps/channels/README.md +15 -0
  71. aria_code/apps/channels/__init__.py +17 -0
  72. aria_code/apps/channels/intake.py +92 -0
  73. aria_code/apps/channels/registry.py +95 -0
  74. aria_code/apps/channels/tradingview.py +94 -0
  75. aria_code/apps/cli/README.md +13 -0
  76. aria_code/apps/cli/__init__.py +2 -0
  77. aria_code/apps/cli/_optional.py +120 -0
  78. aria_code/apps/cli/bootstrap.py +145 -0
  79. aria_code/apps/cli/broker_render.py +206 -0
  80. aria_code/apps/cli/codegen_paths.py +29 -0
  81. aria_code/apps/cli/commands/__init__.py +16 -0
  82. aria_code/apps/cli/commands/_business_render.py +78 -0
  83. aria_code/apps/cli/commands/_ui.py +71 -0
  84. aria_code/apps/cli/commands/analysis_cmds.py +369 -0
  85. aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
  86. aria_code/apps/cli/commands/auth_cmds.py +239 -0
  87. aria_code/apps/cli/commands/backtest_cmds.py +2543 -0
  88. aria_code/apps/cli/commands/broker_cmds.py +1234 -0
  89. aria_code/apps/cli/commands/business_workflow_cmds.py +319 -0
  90. aria_code/apps/cli/commands/canvas_cmds.py +77 -0
  91. aria_code/apps/cli/commands/catalog.py +87 -0
  92. aria_code/apps/cli/commands/core_cmds.py +2520 -0
  93. aria_code/apps/cli/commands/data_cmds.py +457 -0
  94. aria_code/apps/cli/commands/diagnostic_cmds.py +230 -0
  95. aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
  96. aria_code/apps/cli/commands/file_cmds.py +312 -0
  97. aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
  98. aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
  99. aria_code/apps/cli/commands/market.py +434 -0
  100. aria_code/apps/cli/commands/market_cmds.py +1381 -0
  101. aria_code/apps/cli/commands/market_context.py +425 -0
  102. aria_code/apps/cli/commands/market_render.py +7 -0
  103. aria_code/apps/cli/commands/model_cmds.py +2153 -0
  104. aria_code/apps/cli/commands/ops_cmds.py +721 -0
  105. aria_code/apps/cli/commands/orchestrator_cmds.py +179 -0
  106. aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
  107. aria_code/apps/cli/commands/portfolio_cmds.py +1278 -0
  108. aria_code/apps/cli/commands/report.py +437 -0
  109. aria_code/apps/cli/commands/scaffold_templates.py +617 -0
  110. aria_code/apps/cli/commands/session_cmds.py +236 -0
  111. aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
  112. aria_code/apps/cli/commands/team.py +669 -0
  113. aria_code/apps/cli/commands/team_render.py +8 -0
  114. aria_code/apps/cli/commands/ui_cmds.py +401 -0
  115. aria_code/apps/cli/commands/warehouse_cmds.py +79 -0
  116. aria_code/apps/cli/commands/workflow_cmds.py +480 -0
  117. aria_code/apps/cli/commands/workspace_cmds.py +1494 -0
  118. aria_code/apps/cli/config_paths.py +73 -0
  119. aria_code/apps/cli/config_store.py +78 -0
  120. aria_code/apps/cli/constants.py +88 -0
  121. aria_code/apps/cli/context.py +34 -0
  122. aria_code/apps/cli/deterministic.py +191 -0
  123. aria_code/apps/cli/direct.py +48 -0
  124. aria_code/apps/cli/finance_service_catalog.py +111 -0
  125. aria_code/apps/cli/football_reports.py +231 -0
  126. aria_code/apps/cli/github_app_auth.py +135 -0
  127. aria_code/apps/cli/google_login.py +196 -0
  128. aria_code/apps/cli/handlers/__init__.py +11 -0
  129. aria_code/apps/cli/handlers/broker_handlers.py +122 -0
  130. aria_code/apps/cli/handlers/chart_handlers.py +1309 -0
  131. aria_code/apps/cli/handlers/market_handlers.py +2513 -0
  132. aria_code/apps/cli/handlers/realty_handlers.py +114 -0
  133. aria_code/apps/cli/handlers/sports_handlers.py +22 -0
  134. aria_code/apps/cli/handlers/strategy_advice.py +82 -0
  135. aria_code/apps/cli/helpers.py +302 -0
  136. aria_code/apps/cli/hooks.py +180 -0
  137. aria_code/apps/cli/i18n.py +297 -0
  138. aria_code/apps/cli/intent.py +136 -0
  139. aria_code/apps/cli/intent_router.py +276 -0
  140. aria_code/apps/cli/intent_signals.py +344 -0
  141. aria_code/apps/cli/lifecycle_hooks.py +48 -0
  142. aria_code/apps/cli/local_tool_schemas.py +536 -0
  143. aria_code/apps/cli/main.py +29 -0
  144. aria_code/apps/cli/market_metadata.py +135 -0
  145. aria_code/apps/cli/market_universe.py +487 -0
  146. aria_code/apps/cli/message_processing.py +310 -0
  147. aria_code/apps/cli/model_catalog.py +620 -0
  148. aria_code/apps/cli/pdf_report.py +977 -0
  149. aria_code/apps/cli/plan_mode.py +139 -0
  150. aria_code/apps/cli/plotly_html.py +15 -0
  151. aria_code/apps/cli/prediction_feedback.py +202 -0
  152. aria_code/apps/cli/preflight.py +502 -0
  153. aria_code/apps/cli/project_aria.py +60 -0
  154. aria_code/apps/cli/prompt_assembly.py +59 -0
  155. aria_code/apps/cli/prompts/__init__.py +0 -0
  156. aria_code/apps/cli/prompts/coding.py +53 -0
  157. aria_code/apps/cli/prompts/select.py +142 -0
  158. aria_code/apps/cli/prompts/system_prompts.py +552 -0
  159. aria_code/apps/cli/prompts/ui.py +436 -0
  160. aria_code/apps/cli/provider_endpoints.py +237 -0
  161. aria_code/apps/cli/provider_keys.py +73 -0
  162. aria_code/apps/cli/providers/__init__.py +1 -0
  163. aria_code/apps/cli/providers/base.py +521 -0
  164. aria_code/apps/cli/providers/chat_routing.py +128 -0
  165. aria_code/apps/cli/providers/collaboration.py +98 -0
  166. aria_code/apps/cli/providers/llm/__init__.py +1 -0
  167. aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
  168. aria_code/apps/cli/providers/llm/ollama_stream.py +1594 -0
  169. aria_code/apps/cli/providers/llm/response_cache.py +60 -0
  170. aria_code/apps/cli/providers/llm/sse_stream.py +257 -0
  171. aria_code/apps/cli/providers/runtime_bridge.py +353 -0
  172. aria_code/apps/cli/providers/vertexai_stream.py +316 -0
  173. aria_code/apps/cli/runtime_consumer.py +657 -0
  174. aria_code/apps/cli/sandbox.py +32 -0
  175. aria_code/apps/cli/session_export.py +120 -0
  176. aria_code/apps/cli/session_jsonl.py +208 -0
  177. aria_code/apps/cli/session_store.py +112 -0
  178. aria_code/apps/cli/skills_catalog.py +273 -0
  179. aria_code/apps/cli/todo_tracker.py +190 -0
  180. aria_code/apps/cli/tool_executor.py +823 -0
  181. aria_code/apps/cli/tool_registry.py +34 -0
  182. aria_code/apps/cli/tool_scope.py +106 -0
  183. aria_code/apps/cli/tools/__init__.py +40 -0
  184. aria_code/apps/cli/tools/context.py +46 -0
  185. aria_code/apps/cli/tools/file_tools.py +180 -0
  186. aria_code/apps/cli/tools/market_tools.py +620 -0
  187. aria_code/apps/cli/tools/notebook_tools.py +111 -0
  188. aria_code/apps/cli/tools/system_tools.py +726 -0
  189. aria_code/apps/cli/tools/write_tools.py +845 -0
  190. aria_code/apps/cli/tradingview_bridge.py +529 -0
  191. aria_code/apps/cli/turn_planning.py +137 -0
  192. aria_code/apps/cli/update_check.py +153 -0
  193. aria_code/apps/cli/utils/__init__.py +0 -0
  194. aria_code/apps/cli/utils/market_detect.py +1597 -0
  195. aria_code/apps/daemon/README.md +14 -0
  196. aria_code/apps/vscode/README.md +115 -0
  197. aria_code/apps/vscode/package.json +70 -0
  198. aria_code/aria_cli.py +6743 -0
  199. aria_code/aria_daemon.py +1398 -0
  200. aria_code/aria_feishu_bot.py +1423 -0
  201. aria_code/aria_mcp_server.py +7 -0
  202. aria_code/aria_relay_client.py +4 -0
  203. aria_code/aria_relay_server.py +490 -0
  204. aria_code/aria_telegram_bot.py +202 -0
  205. aria_code/ariarc.py +385 -0
  206. aria_code/artifacts.py +581 -0
  207. aria_code/backtest_engine.py +4 -0
  208. aria_code/backtest_report.py +4 -0
  209. aria_code/brokers/__init__.py +78 -0
  210. aria_code/brokers/automation.py +193 -0
  211. aria_code/brokers/base.py +231 -0
  212. aria_code/brokers/capabilities.py +264 -0
  213. aria_code/brokers/cn/__init__.py +10 -0
  214. aria_code/brokers/cn/easytrader_broker.py +193 -0
  215. aria_code/brokers/cn/futu_broker.py +194 -0
  216. aria_code/brokers/cn/longbridge_broker.py +190 -0
  217. aria_code/brokers/cn/tiger_broker.py +196 -0
  218. aria_code/brokers/cn/xtquant_broker.py +225 -0
  219. aria_code/brokers/config.py +414 -0
  220. aria_code/brokers/intl/__init__.py +5 -0
  221. aria_code/brokers/intl/alpaca_broker.py +183 -0
  222. aria_code/brokers/intl/ibkr_broker.py +215 -0
  223. aria_code/brokers/intl/trading212_broker.py +183 -0
  224. aria_code/brokers/intl/webull_broker.py +156 -0
  225. aria_code/brokers/paper_broker.py +266 -0
  226. aria_code/brokers/planning.py +296 -0
  227. aria_code/brokers/registry.py +213 -0
  228. aria_code/brokers/trading.py +267 -0
  229. aria_code/canva_client.py +4 -0
  230. aria_code/change_store.py +127 -0
  231. aria_code/clients/__init__.py +0 -0
  232. aria_code/clients/aliyun_data_client.py +660 -0
  233. aria_code/clients/aria_relay_client.py +182 -0
  234. aria_code/clients/canva_client.py +284 -0
  235. aria_code/clients/figma_client.py +100 -0
  236. aria_code/clients/football_data_client.py +1671 -0
  237. aria_code/clients/kling_video_client.py +182 -0
  238. aria_code/clients/market_data_client.py +1976 -0
  239. aria_code/clients/openai_image_client.py +242 -0
  240. aria_code/clients/runway_video_client.py +157 -0
  241. aria_code/clients/warehouse_erp_client.py +105 -0
  242. aria_code/cloud_memory.py +181 -0
  243. aria_code/command_safety.py +19 -0
  244. aria_code/computer_use_tools.py +4 -0
  245. aria_code/dashboard_generator.py +579 -0
  246. aria_code/data_analysis_tools.py +4 -0
  247. aria_code/data_cleaner.py +483 -0
  248. aria_code/data_service.py +513 -0
  249. aria_code/datasources/__init__.py +23 -0
  250. aria_code/datasources/base.py +166 -0
  251. aria_code/datasources/router.py +253 -0
  252. aria_code/datasources/sources/__init__.py +15 -0
  253. aria_code/datasources/sources/akshare_source.py +269 -0
  254. aria_code/datasources/sources/alpha_vantage_source.py +203 -0
  255. aria_code/datasources/sources/edgar_source.py +219 -0
  256. aria_code/datasources/sources/finnhub_source.py +198 -0
  257. aria_code/datasources/sources/fred_source.py +223 -0
  258. aria_code/datasources/sources/tushare_source.py +143 -0
  259. aria_code/datasources/sources/web_scraper_source.py +278 -0
  260. aria_code/datasources/sources/world_bank_source.py +206 -0
  261. aria_code/datasources/sources/yfinance_source.py +152 -0
  262. aria_code/demo_player.py +207 -0
  263. aria_code/doctor.py +813 -0
  264. aria_code/domain/__init__.py +0 -0
  265. aria_code/domain/backtest_engine.py +354 -0
  266. aria_code/domain/backtest_report.py +472 -0
  267. aria_code/domain/finance_formulas.py +389 -0
  268. aria_code/domain/portfolio_ledger.py +307 -0
  269. aria_code/domain/strategy_vault.py +421 -0
  270. aria_code/evals/__init__.py +37 -0
  271. aria_code/evals/harness.py +598 -0
  272. aria_code/evals/runner.py +238 -0
  273. aria_code/external_agent_runner.py +115 -0
  274. aria_code/figma_client.py +4 -0
  275. aria_code/file_analysis_tools.py +4 -0
  276. aria_code/finance_formulas.py +4 -0
  277. aria_code/football_data_client.py +4 -0
  278. aria_code/image_gen_tools.py +4 -0
  279. aria_code/intent_classifier.py +386 -0
  280. aria_code/kling_video_client.py +4 -0
  281. aria_code/licensing.py +151 -0
  282. aria_code/local_finance_tools.py +4 -0
  283. aria_code/local_image_provider.py +4 -0
  284. aria_code/local_llm_provider.py +4 -0
  285. aria_code/macro_tools.py +4 -0
  286. aria_code/markdown_pdf.py +289 -0
  287. aria_code/market_data_client.py +4 -0
  288. aria_code/mcp_client.py +639 -0
  289. aria_code/memory_manager.py +246 -0
  290. aria_code/model_capability.py +486 -0
  291. aria_code/notification_tools.py +4 -0
  292. aria_code/openai_image_client.py +4 -0
  293. aria_code/packages/__init__.py +23 -0
  294. aria_code/packages/adk_bridge/__init__.py +11 -0
  295. aria_code/packages/adk_bridge/code_review_tools.py +49 -0
  296. aria_code/packages/adk_bridge/market_tools.py +125 -0
  297. aria_code/packages/aria_agents/__init__.py +5 -0
  298. aria_code/packages/aria_agents/manifest.py +78 -0
  299. aria_code/packages/aria_core/__init__.py +34 -0
  300. aria_code/packages/aria_core/architecture.py +269 -0
  301. aria_code/packages/aria_core/export.py +147 -0
  302. aria_code/packages/aria_core/manifest.py +65 -0
  303. aria_code/packages/aria_core/paths.py +57 -0
  304. aria_code/packages/aria_core/secure_file.py +68 -0
  305. aria_code/packages/aria_infra/__init__.py +15 -0
  306. aria_code/packages/aria_infra/arthera.py +52 -0
  307. aria_code/packages/aria_infra/doctor.py +246 -0
  308. aria_code/packages/aria_infra/product.py +40 -0
  309. aria_code/packages/aria_mcp/__init__.py +25 -0
  310. aria_code/packages/aria_mcp/bridge.py +201 -0
  311. aria_code/packages/aria_mcp/circuit.py +82 -0
  312. aria_code/packages/aria_mcp/config.py +111 -0
  313. aria_code/packages/aria_mcp/installer.py +95 -0
  314. aria_code/packages/aria_mcp/server.py +1550 -0
  315. aria_code/packages/aria_mcp/tools.py +81 -0
  316. aria_code/packages/aria_sdk/__init__.py +19 -0
  317. aria_code/packages/aria_sdk/client.py +396 -0
  318. aria_code/packages/aria_sdk/providers.py +70 -0
  319. aria_code/packages/aria_sdk/streaming.py +79 -0
  320. aria_code/packages/aria_sdk/types.py +86 -0
  321. aria_code/packages/aria_services/__init__.py +92 -0
  322. aria_code/packages/aria_services/cache.py +60 -0
  323. aria_code/packages/aria_services/context.py +356 -0
  324. aria_code/packages/aria_services/context_checkpoints.py +157 -0
  325. aria_code/packages/aria_services/data.py +11 -0
  326. aria_code/packages/aria_services/provider_health.py +268 -0
  327. aria_code/packages/aria_services/references.py +247 -0
  328. aria_code/packages/aria_services/registry.py +249 -0
  329. aria_code/packages/aria_services/research_protocol.py +57 -0
  330. aria_code/packages/aria_services/research_quality.py +186 -0
  331. aria_code/packages/aria_services/settings.py +157 -0
  332. aria_code/packages/aria_services/usage.py +151 -0
  333. aria_code/packages/aria_skills/__init__.py +46 -0
  334. aria_code/packages/aria_skills/catalog.py +149 -0
  335. aria_code/packages/aria_skills/loader.py +455 -0
  336. aria_code/packages/aria_skills/registry.py +84 -0
  337. aria_code/packages/aria_tools/__init__.py +5 -0
  338. aria_code/packages/aria_tools/financial/__init__.py +8 -0
  339. aria_code/packages/aria_tools/financial/_paths.py +130 -0
  340. aria_code/packages/aria_tools/financial/compliance.py +31 -0
  341. aria_code/packages/aria_tools/financial/factors.py +33 -0
  342. aria_code/packages/aria_tools/financial/risk_tools.py +33 -0
  343. aria_code/packages/aria_tools/financial/strategy.py +31 -0
  344. aria_code/packages/aria_tools/registry.py +128 -0
  345. aria_code/packages/quant_engine/__init__.py +34 -0
  346. aria_code/packages/quant_engine/_namespace.py +62 -0
  347. aria_code/packages/quant_engine/backtest/__init__.py +14 -0
  348. aria_code/packages/quant_engine/backtest/core.py +53 -0
  349. aria_code/packages/quant_engine/backtest/engine.py +96 -0
  350. aria_code/packages/quant_engine/portfolio/__init__.py +13 -0
  351. aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
  352. aria_code/packages/quant_engine/sports/__init__.py +80 -0
  353. aria_code/packages/quant_engine/sports/calibrator.py +354 -0
  354. aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
  355. aria_code/packages/quant_engine/sports/elo.py +300 -0
  356. aria_code/packages/quant_engine/sports/form.py +188 -0
  357. aria_code/packages/quant_engine/sports/h2h.py +195 -0
  358. aria_code/packages/quant_engine/sports/ml_model.py +377 -0
  359. aria_code/packages/quant_engine/sports/predictor.py +312 -0
  360. aria_code/packages/quant_engine/sports/tracker.py +665 -0
  361. aria_code/packages/quant_engine/stochastic/__init__.py +35 -0
  362. aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  363. aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
  364. aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  365. aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  366. aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
  367. aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  368. aria_code/packs/__init__.py +85 -0
  369. aria_code/packs/base.py +168 -0
  370. aria_code/packs/finance/__init__.py +250 -0
  371. aria_code/packs/logistics/__init__.py +169 -0
  372. aria_code/packs/payments/__init__.py +139 -0
  373. aria_code/packs/realty/__init__.py +190 -0
  374. aria_code/packs/registry.py +202 -0
  375. aria_code/packs/rules.py +90 -0
  376. aria_code/plan_utils.py +194 -0
  377. aria_code/plugin_loader.py +328 -0
  378. aria_code/portfolio_ledger.py +4 -0
  379. aria_code/preview_server.py +449 -0
  380. aria_code/privacy/__init__.py +5 -0
  381. aria_code/privacy/feedback.py +123 -0
  382. aria_code/project_review.py +278 -0
  383. aria_code/project_review_api.py +235 -0
  384. aria_code/project_review_server.py +18 -0
  385. aria_code/project_tools.py +4 -0
  386. aria_code/providers/__init__.py +30 -0
  387. aria_code/providers/llm/__init__.py +19 -0
  388. aria_code/providers/llm/anthropic.py +184 -0
  389. aria_code/providers/llm/autoconfig.py +213 -0
  390. aria_code/providers/llm/base.py +139 -0
  391. aria_code/providers/llm/custom.py +158 -0
  392. aria_code/providers/llm/ollama.py +129 -0
  393. aria_code/providers/llm/openai_compat.py +313 -0
  394. aria_code/providers/llm/registry.py +444 -0
  395. aria_code/providers/local_image_provider.py +202 -0
  396. aria_code/providers/local_llm_provider.py +589 -0
  397. aria_code/realty_data_tools.py +4 -0
  398. aria_code/report_exporters.py +150 -0
  399. aria_code/report_generator.py +1764 -0
  400. aria_code/runtime/__init__.py +169 -0
  401. aria_code/runtime/acceptance.py +481 -0
  402. aria_code/runtime/agent_loop.py +1520 -0
  403. aria_code/runtime/approval.py +57 -0
  404. aria_code/runtime/budget.py +232 -0
  405. aria_code/runtime/checkpoints.py +432 -0
  406. aria_code/runtime/crash_report.py +174 -0
  407. aria_code/runtime/events.py +113 -0
  408. aria_code/runtime/gateway.py +150 -0
  409. aria_code/runtime/lsp.py +484 -0
  410. aria_code/runtime/orchestrator.py +55 -0
  411. aria_code/runtime/repo_map.py +700 -0
  412. aria_code/runtime/run_state.py +127 -0
  413. aria_code/runtime/run_store.py +450 -0
  414. aria_code/runtime/self_healing.py +422 -0
  415. aria_code/runtime/subagent.py +547 -0
  416. aria_code/runtime/task_ledger.py +86 -0
  417. aria_code/runtime/task_orchestrator.py +90 -0
  418. aria_code/runtime/tool_executor.py +217 -0
  419. aria_code/runtime/tool_policy.py +107 -0
  420. aria_code/runtime/worktrees.py +215 -0
  421. aria_code/runway_video_client.py +4 -0
  422. aria_code/safety/__init__.py +23 -0
  423. aria_code/safety/permissions.py +306 -0
  424. aria_code/safety/service.py +102 -0
  425. aria_code/setup_wizard.py +673 -0
  426. aria_code/spreadsheet_tools.py +4 -0
  427. aria_code/strategy_vault.py +4 -0
  428. aria_code/tools/__init__.py +0 -0
  429. aria_code/tools/artifact_tools.py +252 -0
  430. aria_code/tools/broker_tools.py +92 -0
  431. aria_code/tools/build_quant_engine.py +97 -0
  432. aria_code/tools/code_audit_tools.py +192 -0
  433. aria_code/tools/computer_use_tools.py +504 -0
  434. aria_code/tools/data_analysis_tools.py +809 -0
  435. aria_code/tools/enterprise_finance_tools.py +156 -0
  436. aria_code/tools/extended_tools.py +240 -0
  437. aria_code/tools/file_analysis_tools.py +885 -0
  438. aria_code/tools/image_gen_tools.py +114 -0
  439. aria_code/tools/local_finance_tools.py +3417 -0
  440. aria_code/tools/logistics_tools.py +135 -0
  441. aria_code/tools/macro_tools.py +369 -0
  442. aria_code/tools/notification_tools.py +249 -0
  443. aria_code/tools/project_tools.py +525 -0
  444. aria_code/tools/realty_data_tools.py +659 -0
  445. aria_code/tools/spreadsheet_tools.py +388 -0
  446. aria_code/tools/stripe_tools.py +115 -0
  447. aria_code/ui/__init__.py +105 -0
  448. aria_code/ui/banner.py +508 -0
  449. aria_code/ui/completer.py +501 -0
  450. aria_code/ui/console.py +281 -0
  451. aria_code/ui/image_render.py +243 -0
  452. aria_code/ui/input_box.py +488 -0
  453. aria_code/ui/picker.py +201 -0
  454. aria_code/ui/render/__init__.py +11 -0
  455. aria_code/ui/render/finance.py +1705 -0
  456. aria_code/ui/render/market.py +225 -0
  457. aria_code/ui/render/output.py +775 -0
  458. aria_code/ui/render/responsive.py +75 -0
  459. aria_code/ui/render/team.py +354 -0
  460. aria_code/ui/robot.py +235 -0
  461. aria_code/ui/startup_dashboard.py +109 -0
  462. aria_code/utils/vscode_uri.py +37 -0
  463. aria_code/video_analysis.py +147 -0
  464. aria_code/video_editor.py +271 -0
  465. aria_code/workspace/__init__.py +6 -0
  466. aria_code/workspace/files.py +193 -0
  467. aria_code/workspace/verify.py +113 -0
  468. aria_code/workspace_changes.py +199 -0
  469. aria_code/workspace_context.py +233 -0
  470. aria_code/workspace_service.py +196 -0
  471. aria_code/workspace_tools.py +150 -0
  472. aria_code-0.52.0.dist-info/METADATA +237 -0
  473. aria_code-0.52.0.dist-info/RECORD +478 -0
  474. aria_code-0.52.0.dist-info/WHEEL +5 -0
  475. aria_code-0.52.0.dist-info/entry_points.txt +3 -0
  476. aria_code-0.52.0.dist-info/licenses/LICENSE +201 -0
  477. aria_code-0.52.0.dist-info/licenses/NOTICE +49 -0
  478. aria_code-0.52.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,233 @@
1
+ """Safe, user-visible workspace context.
2
+
3
+ The context is deliberately a small allow-list. It is suitable for a UI
4
+ summary, but is not a place for credentials, provider configuration, raw tool
5
+ responses, or data-source details.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ import tempfile
13
+ from datetime import datetime, timezone
14
+ from pathlib import Path
15
+ from typing import Any, Mapping
16
+
17
+
18
+ CONTEXT_VERSION = 1
19
+ CONTEXT_FILE_NAME = "workspace-context.json"
20
+ VALID_ACTIVITY_STATES = {
21
+ "idle",
22
+ "queued",
23
+ "running",
24
+ "needs_approval",
25
+ "completed",
26
+ "attention",
27
+ }
28
+
29
+
30
+ def _timestamp(now: datetime | None = None) -> str:
31
+ value = now or datetime.now(timezone.utc)
32
+ if value.tzinfo is None:
33
+ value = value.replace(tzinfo=timezone.utc)
34
+ return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
35
+
36
+
37
+ def _text(value: Any, *, limit: int) -> str:
38
+ if not isinstance(value, str):
39
+ return ""
40
+ return value.strip()[:limit]
41
+
42
+
43
+ def _count(value: Any, *, maximum: int = 999_999_999) -> int:
44
+ if isinstance(value, bool):
45
+ return 0
46
+ try:
47
+ return max(0, min(int(value), maximum))
48
+ except (TypeError, ValueError):
49
+ return 0
50
+
51
+
52
+ def context_file_path(workspace_root: str | Path | None = None) -> Path:
53
+ """Return the private, workspace-local state path."""
54
+
55
+ root = Path(workspace_root or Path.cwd()).expanduser().resolve()
56
+ return root / ".aria" / CONTEXT_FILE_NAME
57
+
58
+
59
+ def default_workspace_context(
60
+ workspace_root: str | Path | None = None,
61
+ *,
62
+ now: datetime | None = None,
63
+ ) -> dict[str, Any]:
64
+ root = Path(workspace_root or Path.cwd()).expanduser().resolve()
65
+ project_name = root.name or "Workspace"
66
+ return {
67
+ "schemaVersion": CONTEXT_VERSION,
68
+ "updatedAt": _timestamp(now),
69
+ "project": {"name": project_name},
70
+ "activity": {
71
+ "state": "idle",
72
+ "requiresApproval": False,
73
+ },
74
+ "pendingApprovalCount": 0,
75
+ "completedTaskCount": 0,
76
+ "artifacts": [],
77
+ }
78
+
79
+
80
+ def normalize_workspace_context(
81
+ raw: Mapping[str, Any] | None,
82
+ workspace_root: str | Path | None = None,
83
+ *,
84
+ now: datetime | None = None,
85
+ ) -> dict[str, Any]:
86
+ """Return an allow-listed context, discarding unknown or sensitive input."""
87
+
88
+ source: Mapping[str, Any] = raw if isinstance(raw, Mapping) else {}
89
+ normalized = default_workspace_context(workspace_root, now=now)
90
+
91
+ project = source.get("project")
92
+ if isinstance(project, Mapping):
93
+ name = _text(project.get("name"), limit=120)
94
+ symbol = _text(project.get("symbol"), limit=24)
95
+ if name:
96
+ normalized["project"]["name"] = name
97
+ if symbol:
98
+ normalized["project"]["symbol"] = symbol
99
+
100
+ activity = source.get("activity")
101
+ if isinstance(activity, Mapping):
102
+ state = _text(activity.get("state"), limit=32)
103
+ title = _text(activity.get("title"), limit=160)
104
+ if state in VALID_ACTIVITY_STATES:
105
+ normalized["activity"]["state"] = state
106
+ if title:
107
+ normalized["activity"]["title"] = title
108
+ if "progress" in activity:
109
+ normalized["activity"]["progress"] = _count(activity.get("progress"), maximum=100)
110
+ normalized["activity"]["requiresApproval"] = bool(
111
+ activity.get("requiresApproval", False)
112
+ )
113
+
114
+ normalized["pendingApprovalCount"] = _count(source.get("pendingApprovalCount"))
115
+ normalized["completedTaskCount"] = _count(source.get("completedTaskCount"))
116
+
117
+ artifacts = source.get("artifacts")
118
+ if isinstance(artifacts, list):
119
+ safe_artifacts: list[dict[str, str]] = []
120
+ for artifact in artifacts[:6]:
121
+ if not isinstance(artifact, Mapping):
122
+ continue
123
+ artifact_id = _text(artifact.get("id"), limit=120)
124
+ title = _text(artifact.get("title"), limit=160)
125
+ kind = _text(artifact.get("kind"), limit=60)
126
+ if not artifact_id or not title:
127
+ continue
128
+ entry: dict[str, str] = {
129
+ "id": artifact_id,
130
+ "title": title,
131
+ "status": "ready",
132
+ }
133
+ if kind:
134
+ entry["kind"] = kind
135
+ safe_artifacts.append(entry)
136
+ normalized["artifacts"] = safe_artifacts
137
+
138
+ return normalized
139
+
140
+
141
+ def load_workspace_context(workspace_root: str | Path | None = None) -> dict[str, Any]:
142
+ path = context_file_path(workspace_root)
143
+ try:
144
+ with path.open("r", encoding="utf-8") as handle:
145
+ raw = json.load(handle)
146
+ except (FileNotFoundError, OSError, json.JSONDecodeError):
147
+ raw = None
148
+ return normalize_workspace_context(raw, workspace_root)
149
+
150
+
151
+ def save_workspace_context(
152
+ context: Mapping[str, Any] | None,
153
+ workspace_root: str | Path | None = None,
154
+ ) -> Path:
155
+ """Atomically persist only the normalized, user-visible context."""
156
+
157
+ path = context_file_path(workspace_root)
158
+ path.parent.mkdir(parents=True, exist_ok=True)
159
+ safe_context = normalize_workspace_context(context, workspace_root)
160
+ safe_context["updatedAt"] = _timestamp()
161
+
162
+ temp_path: Path | None = None
163
+ try:
164
+ with tempfile.NamedTemporaryFile(
165
+ mode="w",
166
+ encoding="utf-8",
167
+ dir=path.parent,
168
+ prefix=".workspace-context-",
169
+ suffix=".tmp",
170
+ delete=False,
171
+ ) as handle:
172
+ temp_path = Path(handle.name)
173
+ os.chmod(temp_path, 0o600)
174
+ json.dump(safe_context, handle, ensure_ascii=False, indent=2, sort_keys=True)
175
+ handle.write("\n")
176
+ handle.flush()
177
+ os.fsync(handle.fileno())
178
+ os.replace(temp_path, path)
179
+ os.chmod(path, 0o600)
180
+ finally:
181
+ if temp_path is not None and temp_path.exists():
182
+ temp_path.unlink(missing_ok=True)
183
+ return path
184
+
185
+
186
+ def update_workspace_context(
187
+ patch: Mapping[str, Any],
188
+ workspace_root: str | Path | None = None,
189
+ ) -> dict[str, Any]:
190
+ """Apply a shallow, allow-listed patch and persist it."""
191
+
192
+ current = load_workspace_context(workspace_root)
193
+ merged: dict[str, Any] = {**current}
194
+ for key in ("project", "activity"):
195
+ candidate = patch.get(key)
196
+ if isinstance(candidate, Mapping):
197
+ merged[key] = {**current[key], **candidate}
198
+ for key in ("pendingApprovalCount", "completedTaskCount", "artifacts"):
199
+ if key in patch:
200
+ merged[key] = patch[key]
201
+ safe_context = normalize_workspace_context(merged, workspace_root)
202
+ save_workspace_context(safe_context, workspace_root)
203
+ return safe_context
204
+
205
+
206
+ def set_workspace_activity(
207
+ *,
208
+ state: str,
209
+ title: str | None = None,
210
+ progress: int | None = None,
211
+ requires_approval: bool | None = None,
212
+ workspace_root: str | Path | None = None,
213
+ ) -> dict[str, Any]:
214
+ activity: dict[str, Any] = {"state": state}
215
+ if title is not None:
216
+ activity["title"] = title
217
+ if progress is not None:
218
+ activity["progress"] = progress
219
+ if requires_approval is not None:
220
+ activity["requiresApproval"] = requires_approval
221
+ return update_workspace_context({"activity": activity}, workspace_root)
222
+
223
+
224
+ def record_workspace_artifact(
225
+ *,
226
+ artifact_id: str,
227
+ title: str,
228
+ kind: str | None = None,
229
+ workspace_root: str | Path | None = None,
230
+ ) -> dict[str, Any]:
231
+ current = load_workspace_context(workspace_root)
232
+ artifacts = [*current["artifacts"], {"id": artifact_id, "title": title, "kind": kind or ""}]
233
+ return update_workspace_context({"artifacts": artifacts[-6:]}, workspace_root)
@@ -0,0 +1,196 @@
1
+ """Isolated, non-executing source workspaces for remote Aria Code runs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import os
8
+ import re
9
+ import secrets
10
+ import shutil
11
+ import tempfile
12
+ from dataclasses import asdict, dataclass
13
+ from datetime import datetime, timedelta, timezone
14
+ from pathlib import Path, PurePosixPath
15
+ from typing import Any
16
+
17
+ from aria_code.project_review import (
18
+ MAX_ARCHIVE_BYTES,
19
+ MAX_EXTRACTED_BYTES,
20
+ MAX_FILES,
21
+ ProjectReviewError,
22
+ _archive_entries,
23
+ _is_sensitive,
24
+ _safe_member_path,
25
+ _should_ignore,
26
+ )
27
+ from aria_code.workspace import WorkspaceFiles, WorkspaceSecurity
28
+
29
+
30
+ _WORKSPACE_ID = re.compile(r"^ws_[a-f0-9]{24}$")
31
+
32
+
33
+ class WorkspaceServiceError(ValueError):
34
+ pass
35
+
36
+
37
+ def _now() -> datetime:
38
+ return datetime.now(timezone.utc)
39
+
40
+
41
+ def _subject_hash(value: str) -> str:
42
+ return hashlib.sha256(value.strip().encode("utf-8")).hexdigest()
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class WorkspaceManifest:
47
+ schema_version: str
48
+ workspace_id: str
49
+ owner_hash: str
50
+ run_hash: str
51
+ archive_name: str
52
+ file_count: int
53
+ extracted_bytes: int
54
+ skipped_sensitive_files: int
55
+ created_at: str
56
+ expires_at: str
57
+ execution_enabled: bool = False
58
+ network_enabled: bool = False
59
+
60
+ def public_dict(self) -> dict[str, Any]:
61
+ data = asdict(self)
62
+ data.pop("owner_hash", None)
63
+ data.pop("run_hash", None)
64
+ return data
65
+
66
+
67
+ class WorkspaceService:
68
+ """Owns bounded project trees; never executes their contents."""
69
+
70
+ def __init__(self, root: str | Path | None = None, ttl_hours: int = 24) -> None:
71
+ configured = root or os.getenv("ARIA_CODE_WORKSPACE_ROOT")
72
+ self.root = Path(configured or (Path.home() / ".aria" / "runtime" / "workspaces")).expanduser().resolve()
73
+ self.ttl_hours = max(1, min(int(ttl_hours), 168))
74
+ self.root.mkdir(parents=True, exist_ok=True, mode=0o700)
75
+ try:
76
+ self.root.chmod(0o700)
77
+ except OSError:
78
+ pass
79
+
80
+ def _workspace_dir(self, workspace_id: str) -> Path:
81
+ if not _WORKSPACE_ID.fullmatch(workspace_id):
82
+ raise WorkspaceServiceError("Invalid workspace id.")
83
+ target = (self.root / workspace_id).resolve()
84
+ if target.parent != self.root:
85
+ raise WorkspaceServiceError("Invalid workspace path.")
86
+ return target
87
+
88
+ @staticmethod
89
+ def _manifest_path(directory: Path) -> Path:
90
+ return directory / ".aria-workspace.json"
91
+
92
+ def create_from_archive(self, payload: bytes, filename: str, *, owner_id: str, run_id: str) -> WorkspaceManifest:
93
+ if not owner_id.strip() or not run_id.strip():
94
+ raise WorkspaceServiceError("owner_id and run_id are required.")
95
+ if not payload or len(payload) > MAX_ARCHIVE_BYTES:
96
+ raise WorkspaceServiceError("Project archive is empty or exceeds the upload limit.")
97
+
98
+ workspace_id = f"ws_{secrets.token_hex(12)}"
99
+ final_dir = self._workspace_dir(workspace_id)
100
+ staging = Path(tempfile.mkdtemp(prefix=f".{workspace_id}-", dir=self.root))
101
+ source_root = staging / "source"
102
+ source_root.mkdir(mode=0o700)
103
+ count = total = skipped_sensitive = 0
104
+ try:
105
+ for raw_path, data in _archive_entries(payload, filename):
106
+ relative = _safe_member_path(raw_path)
107
+ if _should_ignore(relative):
108
+ continue
109
+ if _is_sensitive(relative):
110
+ skipped_sensitive += 1
111
+ continue
112
+ count += 1
113
+ total += len(data)
114
+ if count > MAX_FILES or total > MAX_EXTRACTED_BYTES:
115
+ raise WorkspaceServiceError("Expanded project exceeds workspace limits.")
116
+ target = (source_root / Path(*relative.parts)).resolve()
117
+ if source_root != target and source_root not in target.parents:
118
+ raise WorkspaceServiceError("Archive path escapes the workspace.")
119
+ target.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
120
+ target.write_bytes(data)
121
+ try:
122
+ target.chmod(0o600)
123
+ except OSError:
124
+ pass
125
+ if count == 0:
126
+ raise WorkspaceServiceError("No safe project files were found.")
127
+ created = _now()
128
+ manifest = WorkspaceManifest(
129
+ schema_version="aria.workspace.v1",
130
+ workspace_id=workspace_id,
131
+ owner_hash=_subject_hash(owner_id),
132
+ run_hash=_subject_hash(run_id),
133
+ archive_name=PurePosixPath(filename).name[:180],
134
+ file_count=count,
135
+ extracted_bytes=total,
136
+ skipped_sensitive_files=skipped_sensitive,
137
+ created_at=created.isoformat(),
138
+ expires_at=(created + timedelta(hours=self.ttl_hours)).isoformat(),
139
+ )
140
+ self._manifest_path(staging).write_text(json.dumps(asdict(manifest), indent=2), encoding="utf-8")
141
+ staging.rename(final_dir)
142
+ return manifest
143
+ except (ProjectReviewError, OSError) as exc:
144
+ shutil.rmtree(staging, ignore_errors=True)
145
+ if isinstance(exc, ProjectReviewError):
146
+ raise WorkspaceServiceError(str(exc)) from exc
147
+ raise WorkspaceServiceError("Could not create isolated workspace.") from exc
148
+ except Exception:
149
+ shutil.rmtree(staging, ignore_errors=True)
150
+ raise
151
+
152
+ def get(self, workspace_id: str, *, owner_id: str) -> WorkspaceManifest:
153
+ directory = self._workspace_dir(workspace_id)
154
+ try:
155
+ raw = json.loads(self._manifest_path(directory).read_text(encoding="utf-8"))
156
+ manifest = WorkspaceManifest(**raw)
157
+ except (OSError, TypeError, ValueError, json.JSONDecodeError) as exc:
158
+ raise WorkspaceServiceError("Workspace not found.") from exc
159
+ if not secrets.compare_digest(manifest.owner_hash, _subject_hash(owner_id)):
160
+ # Do not reveal whether another tenant owns the identifier.
161
+ raise WorkspaceServiceError("Workspace not found.")
162
+ return manifest
163
+
164
+ def files(self, workspace_id: str, *, owner_id: str) -> WorkspaceFiles:
165
+ self.get(workspace_id, owner_id=owner_id)
166
+ source_root = self._workspace_dir(workspace_id) / "source"
167
+ return WorkspaceFiles(WorkspaceSecurity(cwd=source_root, allow_home=False))
168
+
169
+ def source_path(self, workspace_id: str, relative_path: str, *, owner_id: str) -> Path:
170
+ self.get(workspace_id, owner_id=owner_id)
171
+ source_root = (self._workspace_dir(workspace_id) / "source").resolve()
172
+ relative = PurePosixPath(str(relative_path or ".").replace("\\", "/"))
173
+ if relative.is_absolute() or ".." in relative.parts:
174
+ raise WorkspaceServiceError("Path must stay inside the workspace.")
175
+ target = (source_root / Path(*relative.parts)).resolve()
176
+ if target != source_root and source_root not in target.parents:
177
+ raise WorkspaceServiceError("Path must stay inside the workspace.")
178
+ return target
179
+
180
+ def delete(self, workspace_id: str, *, owner_id: str) -> None:
181
+ self.get(workspace_id, owner_id=owner_id)
182
+ shutil.rmtree(self._workspace_dir(workspace_id))
183
+
184
+ def cleanup_expired(self, now: datetime | None = None) -> int:
185
+ threshold = now or _now()
186
+ removed = 0
187
+ for directory in self.root.glob("ws_*"):
188
+ try:
189
+ raw = json.loads(self._manifest_path(directory).read_text(encoding="utf-8"))
190
+ expires = datetime.fromisoformat(str(raw["expires_at"]))
191
+ if expires <= threshold:
192
+ shutil.rmtree(directory)
193
+ removed += 1
194
+ except (OSError, KeyError, ValueError, json.JSONDecodeError):
195
+ continue
196
+ return removed
@@ -0,0 +1,150 @@
1
+ """Read-only tool profile bound to one isolated remote workspace."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from aria_code.workspace_service import WorkspaceService, WorkspaceServiceError
8
+ from aria_code.workspace_changes import WorkspaceChangeError, WorkspaceChangeService
9
+
10
+
11
+ def build_workspace_read_tools(
12
+ service: WorkspaceService,
13
+ workspace_id: str,
14
+ owner_id: str,
15
+ ) -> tuple[dict[str, tuple[Any, dict[str, Any]]], list[dict[str, Any]]]:
16
+ """Return ToolExecutor-compatible handlers without exposing host paths."""
17
+
18
+ def read_file(params: dict[str, Any]) -> dict[str, Any]:
19
+ relative = str(params.get("path") or "")
20
+ if not relative:
21
+ return {"success": False, "error": "path is required"}
22
+ try:
23
+ target = service.source_path(workspace_id, relative, owner_id=owner_id)
24
+ result = service.files(workspace_id, owner_id=owner_id).read_file(
25
+ str(target),
26
+ offset=max(0, int(params.get("offset") or 0)),
27
+ limit=max(0, min(int(params.get("limit") or 0), 2000)),
28
+ )
29
+ return {"success": True, "path": relative, "lines": result.lines, "content": result.content}
30
+ except (WorkspaceServiceError, OSError, ValueError) as exc:
31
+ return {"success": False, "error": str(exc)}
32
+
33
+ def list_files(params: dict[str, Any]) -> dict[str, Any]:
34
+ relative = str(params.get("path") or ".")
35
+ pattern = str(params.get("pattern") or "*")[:200]
36
+ try:
37
+ target = service.source_path(workspace_id, relative, owner_id=owner_id)
38
+ result = service.files(workspace_id, owner_id=owner_id).list_files(str(target), pattern)
39
+ result["path"] = relative
40
+ return {"success": True, **result}
41
+ except (WorkspaceServiceError, OSError, ValueError) as exc:
42
+ return {"success": False, "error": str(exc)}
43
+
44
+ def search_code(params: dict[str, Any]) -> dict[str, Any]:
45
+ relative = str(params.get("path") or ".")
46
+ pattern = str(params.get("pattern") or "")[:500]
47
+ file_glob = str(params.get("file_glob") or "**/*")[:200]
48
+ try:
49
+ target = service.source_path(workspace_id, relative, owner_id=owner_id)
50
+ result = service.files(workspace_id, owner_id=owner_id).search_code(
51
+ pattern, str(target), file_glob
52
+ )
53
+ result["path"] = relative
54
+ return {"success": True, **result}
55
+ except (WorkspaceServiceError, OSError, ValueError) as exc:
56
+ return {"success": False, "error": str(exc)}
57
+
58
+ schemas = [
59
+ {
60
+ "type": "function",
61
+ "function": {
62
+ "name": "workspace_read_file",
63
+ "description": "Read a UTF-8 text file inside the assigned project workspace.",
64
+ "parameters": {
65
+ "type": "object",
66
+ "properties": {
67
+ "path": {"type": "string"},
68
+ "offset": {"type": "integer", "minimum": 0},
69
+ "limit": {"type": "integer", "minimum": 0, "maximum": 2000},
70
+ },
71
+ "required": ["path"],
72
+ },
73
+ },
74
+ },
75
+ {
76
+ "type": "function",
77
+ "function": {
78
+ "name": "workspace_list_files",
79
+ "description": "List files below a relative directory in the assigned workspace.",
80
+ "parameters": {
81
+ "type": "object",
82
+ "properties": {"path": {"type": "string"}, "pattern": {"type": "string"}},
83
+ },
84
+ },
85
+ },
86
+ {
87
+ "type": "function",
88
+ "function": {
89
+ "name": "workspace_search_code",
90
+ "description": "Search project text using a regular expression without executing code.",
91
+ "parameters": {
92
+ "type": "object",
93
+ "properties": {
94
+ "pattern": {"type": "string"},
95
+ "path": {"type": "string"},
96
+ "file_glob": {"type": "string"},
97
+ },
98
+ "required": ["pattern"],
99
+ },
100
+ },
101
+ },
102
+ ]
103
+ handlers = {
104
+ "workspace_read_file": (read_file, schemas[0]),
105
+ "workspace_list_files": (list_files, schemas[1]),
106
+ "workspace_search_code": (search_code, schemas[2]),
107
+ }
108
+ return handlers, schemas
109
+
110
+
111
+ def build_workspace_proposal_tools(
112
+ service: WorkspaceService,
113
+ workspace_id: str,
114
+ owner_id: str,
115
+ ) -> tuple[dict[str, tuple[Any, dict[str, Any]]], list[dict[str, Any]]]:
116
+ """Expose proposal creation only; applying still requires control-plane approval."""
117
+ schema = {
118
+ "type": "function",
119
+ "function": {
120
+ "name": "workspace_propose_change",
121
+ "description": (
122
+ "Stage a complete UTF-8 file replacement and return its diff. "
123
+ "This does not modify the project; a user must approve the returned change id."
124
+ ),
125
+ "parameters": {
126
+ "type": "object",
127
+ "properties": {
128
+ "path": {"type": "string"},
129
+ "content": {"type": "string"},
130
+ "expected_sha256": {"type": "string"},
131
+ },
132
+ "required": ["path", "content"],
133
+ },
134
+ },
135
+ }
136
+
137
+ def propose(params: dict[str, Any]) -> dict[str, Any]:
138
+ try:
139
+ change = WorkspaceChangeService(service).propose(
140
+ workspace_id,
141
+ owner_id=owner_id,
142
+ path=str(params.get("path") or ""),
143
+ content=str(params.get("content") or ""),
144
+ expected_sha256=(str(params["expected_sha256"]) if params.get("expected_sha256") else None),
145
+ )
146
+ return {"success": True, **change.public_dict(), "requires_approval": True}
147
+ except (WorkspaceChangeError, WorkspaceServiceError, OSError, ValueError) as exc:
148
+ return {"success": False, "error": str(exc)}
149
+
150
+ return {"workspace_propose_change": (propose, schema)}, [schema]