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
aria_bots/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Aria's chat bots: Feishu and Telegram adapters, the relay client, and the
2
+ logistics tools they answer shipper questions with.
3
+
4
+ This package runs as its own service (services/aria-bots). It does not import
5
+ aria_code; aria_code keeps shims at the old module paths until the Python core
6
+ is removed.
7
+ """
@@ -0,0 +1,97 @@
1
+ # Channel Apps
2
+
3
+ Target role: adapters for non-terminal entrypoints.
4
+
5
+ Examples:
6
+
7
+ - relay server/client;
8
+ - Feishu bot;
9
+ - Telegram bot;
10
+ - webhooks;
11
+ - future desktop or browser UI.
12
+
13
+ Each channel should translate inbound messages into gateway requests and render
14
+ gateway responses back to the channel. It should not bypass safety, runtime, or
15
+ artifact policies.
16
+
17
+ ## The conversation layer (`conversation.py`)
18
+
19
+ Everything a chat bot decides that does not depend on the platform lives in
20
+ one module, so every channel — Feishu today, Arthera's own bot later — gets
21
+ the same rules from the same code:
22
+
23
+ | Rule | Where |
24
+ |------|-------|
25
+ | In a group, answer only when addressed; in a direct chat, always | `should_respond` |
26
+ | One group = one shipper (货主), bound by an admin with `/owner <id>` | `handle_owner_command`, `ConversationStore` |
27
+ | Admins are per channel: `ARIA_CHANNEL_ADMINS=feishu:ou_x,telegram:42` | `is_admin` |
28
+ | Context is the conversation's own recent messages, never another's; capped at 20 messages and 7 days | `ConversationStore.history` |
29
+ | A bound conversation runs every turn with `ARIA_OWNER_SCOPE`, enforced by the logistics tools themselves | `ChannelTurn.env`, `aria_bots/logistics/tenancy.py` |
30
+
31
+ A new channel is an adapter: turn the platform's event into an
32
+ `InboundMessage`, then
33
+
34
+ ```python
35
+ if not should_respond(msg):
36
+ return
37
+ reply = handle_owner_command(msg, store) # /owner commands
38
+ if reply is None:
39
+ turn = prepare_turn(msg, store) # context + shipper scope
40
+ reply = await run_model(turn.prompt, history=turn.history, env=turn.env)
41
+ store.record(msg.key, "assistant", reply)
42
+ send(reply)
43
+ ```
44
+
45
+ `aria_feishu_bot.feishu_inbound` is the Feishu version of that first step.
46
+ Conversation keys are `<channel>:<conversation id>`, so the same chat id on
47
+ two platforms is two conversations.
48
+
49
+ ## The shipper digest (`digest.py`)
50
+
51
+ The first thing the conversation layer enables that a reply-only bot cannot:
52
+ each group bound to a shipper is sent, unprompted, what needs attention —
53
+ reorders, SKUs with too little history to plan, dead stock, waybills to query,
54
+ and safe carrier switches. `run_digests(store, senders)` walks the bindings and
55
+ hands each digest to that channel's sender, so a new channel registers a
56
+ `(conversation_id, title, body) -> bool` function and gets the digest too.
57
+
58
+ Data comes from the operator's `ARIA_SHIPPER_FEEDS` file, never from chat, and
59
+ is read through the logistics tools under `ARIA_OWNER_SCOPE`, so a shared
60
+ multi-shipper export yields only that shipper's lines. Configuration problems
61
+ go to the log, not the client's group; a day with nothing to report sends
62
+ nothing.
63
+
64
+ ## Approvals (`approvals.py`)
65
+
66
+ Bot runs cannot use confirmation-required tools, so a bot that may only
67
+ suggest still leaves someone retyping its suggestion. Approvals are the
68
+ sanctioned way through: the bot stores exactly what it proposes, an
69
+ authorised person presses approve or reject in the chat, and only then does
70
+ the action run — once, under the shipper's scope, with who and when recorded.
71
+
72
+ | Rule | Why |
73
+ |------|-----|
74
+ | Approvers per channel (`ARIA_CHANNEL_APPROVERS`, plus admins); none by default | fail closed |
75
+ | Only in the conversation that raised it | a forwarded card cannot be approved elsewhere |
76
+ | A single conditional UPDATE claims the request | two presses cannot both execute |
77
+ | Expires after 24 h | stale numbers should be recomputed, not approved |
78
+ | The conversation must still be bound to the same shipper | a rebound group cannot run the old client's action |
79
+ | The payload is frozen at request time | what was approved is what runs |
80
+ | Failures are recorded, not shown in the chat | exceptions can name host paths |
81
+
82
+ A channel renders the request with two buttons and calls
83
+ `ApprovalStore.decide`; actions are registered in `EXECUTORS`. The first,
84
+ `purchase_order_draft`, writes a CSV to `ARIA_APPROVAL_OUTBOX/<shipper>/` for
85
+ the 3PL's purchasing system: Aria has no integration that places orders, and
86
+ does not pretend to.
87
+
88
+ ## Relay mode
89
+
90
+ In relay mode the user's machine has no Feishu app credentials — the app
91
+ secret stays on the relay — so `aria_feishu_bot._send_message` hands every
92
+ message back over the relay WebSocket and the relay sends it. The relay
93
+ accepts a send only where the client has standing: a reply to a message it
94
+ forwarded to that client (within an hour), or a post to a chat that client's
95
+ bound user has spoken to the bot in; text or cards, rate-limited. A press on a
96
+ card is routed to the client that posted the card, which holds the approval.
97
+ Attachments still need the app's credentials and are declined with a message.
@@ -0,0 +1 @@
1
+ """Conversations, approval cards and shipper digests shared by the chat bots."""
@@ -0,0 +1,263 @@
1
+ """Approvals: a bot may propose an action with consequences, a person decides.
2
+
3
+ Bot runs cannot use confirmation-required tools, because nobody in a chat can
4
+ answer a terminal prompt. That stops harm and also stops the useful half of
5
+ the job — a reorder list someone still has to retype. This is the other way
6
+ through: the bot writes down exactly what it proposes, an authorised person
7
+ approves or rejects it in the chat, and only then does it happen, with a
8
+ record of who decided what and when.
9
+
10
+ Platform-neutral like the rest of apps/channels. A channel renders a request
11
+ with approve/reject buttons and calls `decide` when one is pressed; the rules
12
+ below hold whichever platform the button was on:
13
+
14
+ - Who may decide: ARIA_CHANNEL_APPROVERS, plus ARIA_CHANNEL_ADMINS, per
15
+ channel ("feishu:ou_x"). Nobody, when neither is set.
16
+ - Where: only in the conversation the request was raised in. A card
17
+ forwarded to another chat cannot be approved there.
18
+ - Once: the transition out of "pending" is a single conditional UPDATE, so
19
+ two clicks, or two approvers at the same moment, cannot both execute.
20
+ - Not late: a request expires after `ttl_seconds` (24 h).
21
+ - Not for someone else: the conversation must still be bound to the shipper
22
+ the request was made for, and the action runs under ARIA_OWNER_SCOPE.
23
+ - What was approved is what runs: the payload is frozen at request time and
24
+ executed as stored, never recomputed — the approver saw these lines.
25
+
26
+ Actions are registered in EXECUTORS. The first, "purchase_order_draft", writes
27
+ a CSV draft to an outbox directory (ARIA_APPROVAL_OUTBOX) for the 3PL's own
28
+ purchasing system to pick up. Aria does not place orders: it has no
29
+ integration that could, and pretending otherwise would be the wrong default.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import csv
35
+ import json
36
+ import os
37
+ import secrets
38
+ import sqlite3
39
+ import time
40
+ from contextlib import contextmanager
41
+ from dataclasses import dataclass
42
+ from pathlib import Path
43
+ from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple
44
+
45
+ from aria_bots.channels.conversation import ADMINS_ENV, default_db_path
46
+ from aria_bots.logistics.tenancy import OWNER_SCOPE_ENV
47
+
48
+ APPROVERS_ENV = "ARIA_CHANNEL_APPROVERS"
49
+ OUTBOX_ENV = "ARIA_APPROVAL_OUTBOX"
50
+
51
+ PENDING, APPROVED, REJECTED, EXPIRED, FAILED = "pending", "approved", "rejected", "expired", "failed"
52
+
53
+
54
+ def can_decide(channel: str, sender_ids: Tuple[str, ...], env: Optional[Dict[str, str]] = None) -> bool:
55
+ env = env if env is not None else os.environ
56
+ allowed = set()
57
+ for key in (APPROVERS_ENV, ADMINS_ENV):
58
+ allowed |= {item.strip() for item in env.get(key, "").split(",") if item.strip()}
59
+ return any(f"{channel}:{sid}" in allowed for sid in sender_ids if sid)
60
+
61
+
62
+ @dataclass(frozen=True)
63
+ class Approval:
64
+ id: str
65
+ conversation: str
66
+ owner_id: str
67
+ kind: str
68
+ payload: Dict[str, Any]
69
+ summary: str
70
+ requested_by: str
71
+ requested_at: float
72
+ status: str
73
+ decided_by: str = ""
74
+ decided_at: float = 0.0
75
+ result: str = ""
76
+
77
+
78
+ @dataclass(frozen=True)
79
+ class Decision:
80
+ ok: bool
81
+ message: str
82
+ approval: Optional[Approval] = None
83
+
84
+
85
+ @contextmanager
86
+ def _confined_to(owner_id: str) -> Iterator[None]:
87
+ previous = os.environ.get(OWNER_SCOPE_ENV)
88
+ os.environ[OWNER_SCOPE_ENV] = owner_id
89
+ try:
90
+ yield
91
+ finally:
92
+ if previous is None:
93
+ os.environ.pop(OWNER_SCOPE_ENV, None)
94
+ else:
95
+ os.environ[OWNER_SCOPE_ENV] = previous
96
+
97
+
98
+ # ── Executors ────────────────────────────────────────────────────────────────
99
+
100
+ def outbox_dir() -> Path:
101
+ configured = os.environ.get(OUTBOX_ENV, "").strip()
102
+ return Path(configured).expanduser() if configured else Path.home() / ".aria" / "outbox"
103
+
104
+
105
+ def execute_purchase_order_draft(approval: Approval) -> str:
106
+ """Write the approved lines, exactly as approved, as a CSV draft."""
107
+ lines = approval.payload.get("lines") or []
108
+ if not lines:
109
+ raise ValueError("nothing to order")
110
+ folder = outbox_dir() / approval.owner_id
111
+ folder.mkdir(parents=True, exist_ok=True)
112
+ os.chmod(folder, 0o700)
113
+ stamp = time.strftime("%Y%m%d", time.localtime(approval.decided_at or time.time()))
114
+ path = folder / f"PO-{approval.owner_id}-{stamp}-{approval.id[:8]}.csv"
115
+ with path.open("x", newline="", encoding="utf-8") as handle: # never overwrite a draft
116
+ writer = csv.writer(handle)
117
+ writer.writerow(["owner_id", "sku", "order_qty", "approval_id", "approved_by", "approved_at"])
118
+ decided = time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime(approval.decided_at))
119
+ for line in lines:
120
+ writer.writerow([approval.owner_id, line["sku"], line["qty"], approval.id,
121
+ approval.decided_by, decided])
122
+ return str(path)
123
+
124
+
125
+ EXECUTORS: Dict[str, Callable[[Approval], str]] = {
126
+ "purchase_order_draft": execute_purchase_order_draft,
127
+ }
128
+
129
+
130
+ def propose_reorder(owner_id: str, inventory_path: str) -> Optional[Tuple[str, Dict[str, Any]]]:
131
+ """A purchase-order draft from the shipper's reorder list, or None if nothing to order."""
132
+ from aria_bots.logistics.inventory import tool_plan_inventory_policy
133
+
134
+ with _confined_to(owner_id):
135
+ result = tool_plan_inventory_policy({"file_path": inventory_path})
136
+ if not result.get("success"):
137
+ raise ValueError(result.get("error") or "inventory analysis failed")
138
+ lines = [{"sku": i["sku"], "qty": int(i["suggested_order_qty"])}
139
+ for i in result["data"]["items"]
140
+ if i.get("action") == "reorder" and i.get("suggested_order_qty")]
141
+ if not lines:
142
+ return None
143
+ summary = "\n".join(f"- {line['sku']}:{line['qty']}" for line in lines)
144
+ return summary, {"lines": lines}
145
+
146
+
147
+ # ── Store ────────────────────────────────────────────────────────────────────
148
+
149
+ class ApprovalStore:
150
+ """Requests and decisions, in the same SQLite file as the conversations."""
151
+
152
+ def __init__(self, path: Optional[Path | str] = None, *, ttl_seconds: float = 24 * 3600,
153
+ clock: Callable[[], float] = time.time) -> None:
154
+ self.path = Path(path) if path else default_db_path()
155
+ self.path.parent.mkdir(parents=True, exist_ok=True)
156
+ self.ttl_seconds = float(ttl_seconds)
157
+ self._clock = clock
158
+ with self._db() as db:
159
+ db.execute(
160
+ """CREATE TABLE IF NOT EXISTS approvals (
161
+ id TEXT PRIMARY KEY, conversation TEXT NOT NULL, owner_id TEXT NOT NULL,
162
+ kind TEXT NOT NULL, payload TEXT NOT NULL, summary TEXT NOT NULL,
163
+ requested_by TEXT NOT NULL, requested_at REAL NOT NULL,
164
+ status TEXT NOT NULL, decided_by TEXT NOT NULL DEFAULT '',
165
+ decided_at REAL NOT NULL DEFAULT 0, result TEXT NOT NULL DEFAULT '')"""
166
+ )
167
+ try:
168
+ os.chmod(self.path, 0o600)
169
+ except OSError:
170
+ pass
171
+
172
+ def _db(self) -> sqlite3.Connection:
173
+ return sqlite3.connect(str(self.path))
174
+
175
+ def request(self, *, conversation: str, owner_id: str, kind: str, payload: Dict[str, Any],
176
+ summary: str, requested_by: str) -> Approval:
177
+ if kind not in EXECUTORS:
178
+ raise ValueError(f"unknown action {kind!r}")
179
+ approval_id = secrets.token_urlsafe(12) # unguessable: it travels in a card button
180
+ with self._db() as db:
181
+ db.execute(
182
+ "INSERT INTO approvals (id, conversation, owner_id, kind, payload, summary, "
183
+ "requested_by, requested_at, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
184
+ (approval_id, conversation, owner_id, kind, json.dumps(payload, ensure_ascii=False),
185
+ summary, requested_by, self._clock(), PENDING),
186
+ )
187
+ return self.get(approval_id) # type: ignore[return-value]
188
+
189
+ def get(self, approval_id: str) -> Optional[Approval]:
190
+ with self._db() as db:
191
+ row = db.execute(
192
+ "SELECT id, conversation, owner_id, kind, payload, summary, requested_by, requested_at, "
193
+ "status, decided_by, decided_at, result FROM approvals WHERE id = ?", (approval_id,)
194
+ ).fetchone()
195
+ if not row:
196
+ return None
197
+ values = list(row)
198
+ values[4] = json.loads(values[4])
199
+ return Approval(*values)
200
+
201
+ def recent(self, conversation: str, limit: int = 5) -> List[Approval]:
202
+ with self._db() as db:
203
+ ids = [r[0] for r in db.execute(
204
+ "SELECT id FROM approvals WHERE conversation = ? ORDER BY requested_at DESC LIMIT ?",
205
+ (conversation, limit))]
206
+ return [a for a in (self.get(i) for i in ids) if a]
207
+
208
+ def _transition(self, approval_id: str, status: str, by: str, result: str = "",
209
+ expect: str = PENDING) -> bool:
210
+ with self._db() as db:
211
+ cursor = db.execute(
212
+ "UPDATE approvals SET status = ?, decided_by = ?, decided_at = ?, result = ? "
213
+ "WHERE id = ? AND status = ?",
214
+ (status, by, self._clock(), result, approval_id, expect),
215
+ )
216
+ return cursor.rowcount == 1
217
+
218
+ def decide(self, approval_id: str, *, conversation: str, current_owner: Optional[str],
219
+ decided_by: str, approve: bool, authorised: bool) -> Decision:
220
+ """Apply one approve/reject press. Every refusal says why, and changes nothing."""
221
+ approval = self.get(approval_id)
222
+ if approval is None or approval.conversation != conversation:
223
+ # The same answer for "no such request" and "not this chat's": a
224
+ # forwarded card learns nothing about where it came from.
225
+ return Decision(False, "找不到这条审批,或它不属于本会话。")
226
+ if not authorised:
227
+ return Decision(False, "你没有审批权限。", approval)
228
+ if approval.status != PENDING:
229
+ who = f"({approval.decided_by})" if approval.decided_by else ""
230
+ return Decision(False, f"这条审批已经是「{_STATUS_LABEL.get(approval.status, approval.status)}」{who}。", approval)
231
+ if self._clock() - approval.requested_at > self.ttl_seconds:
232
+ self._transition(approval_id, EXPIRED, "system")
233
+ return Decision(False, "这条审批已过期,请重新发起。", self.get(approval_id))
234
+ if current_owner != approval.owner_id:
235
+ return Decision(False, "本会话绑定的货主已变更,这条审批不能再执行。", approval)
236
+
237
+ if not approve:
238
+ if not self._transition(approval_id, REJECTED, decided_by):
239
+ return Decision(False, "这条审批刚刚已被处理。", self.get(approval_id))
240
+ return Decision(True, "已驳回。", self.get(approval_id))
241
+
242
+ # Claim it first; only the press that wins the claim executes.
243
+ if not self._transition(approval_id, APPROVED, decided_by, result="executing"):
244
+ return Decision(False, "这条审批刚刚已被处理。", self.get(approval_id))
245
+ claimed = self.get(approval_id)
246
+ try:
247
+ with _confined_to(approval.owner_id):
248
+ outcome = EXECUTORS[approval.kind](claimed) # type: ignore[arg-type]
249
+ except Exception as exc:
250
+ # The reason is kept in the record for the operator; the chat only
251
+ # learns that it failed, since an exception can name host paths.
252
+ self._transition(approval_id, FAILED, decided_by, result=str(exc)[:500], expect=APPROVED)
253
+ return Decision(False, "已批准,但执行失败,原因已记录。", self.get(approval_id))
254
+ with self._db() as db:
255
+ db.execute("UPDATE approvals SET result = ? WHERE id = ?", (outcome, approval_id))
256
+ return Decision(True, "已批准并执行。", self.get(approval_id))
257
+
258
+
259
+ _STATUS_LABEL = {PENDING: "待审批", APPROVED: "已批准", REJECTED: "已驳回", EXPIRED: "已过期", FAILED: "执行失败"}
260
+
261
+
262
+ def status_label(status: str) -> str:
263
+ return _STATUS_LABEL.get(status, status)
@@ -0,0 +1,280 @@
1
+ """The platform-neutral half of every chat channel.
2
+
3
+ A chat bot does the same five things on any platform: decide whether a
4
+ message is for it, know which client a conversation belongs to, remember what
5
+ was just said there, keep each conversation's memory to itself, and hand the
6
+ model a turn. Only parsing the platform's event and sending its reply differ.
7
+ This module is the part that does not differ, so Feishu, Telegram, and a bot
8
+ on Arthera's own app or website share one implementation of the rules that
9
+ matter — and a new channel is an adapter of a few dozen lines:
10
+
11
+ msg = InboundMessage(channel="myapp", conversation_id=room_id, kind="group",
12
+ sender_id=user_id, text=text, mentions_bot=True)
13
+ if not should_respond(msg):
14
+ return
15
+ turn = prepare_turn(msg, store)
16
+ reply = await run_my_model(turn.prompt, history=turn.history, env=turn.env)
17
+ store.record(msg.key, "assistant", reply)
18
+
19
+ The guarantees, each tested in tests/test_conversation_core.py:
20
+
21
+ - In a group the bot answers only when addressed; in a direct chat, always.
22
+ - A conversation bound to a shipper (货主) runs every turn with
23
+ ARIA_OWNER_SCOPE set, which the logistics tools enforce themselves
24
+ (tools/logistics_tenancy.py) — the binding does not rely on the prompt.
25
+ - Only channel admins (ARIA_CHANNEL_ADMINS) can bind or unbind.
26
+ - History is per conversation and never crosses: the context for a turn is
27
+ built from that conversation's own messages and nothing else.
28
+ - History is bounded twice: the last `max_turns` messages, none older than
29
+ `ttl_seconds`. A chat bot that keeps every client message forever is a
30
+ liability, not a feature.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import os
36
+ import re
37
+ import sqlite3
38
+ import time
39
+ from dataclasses import dataclass, field
40
+ from pathlib import Path
41
+ from typing import Callable, Dict, List, Optional, Tuple
42
+
43
+ from aria_bots.channels.registry import CHANNEL_TASK_SCHEMA
44
+ from aria_bots.logistics.tenancy import OWNER_SCOPE_ENV
45
+
46
+ ADMINS_ENV = "ARIA_CHANNEL_ADMINS"
47
+ DB_ENV = "ARIA_CONVERSATIONS_DB"
48
+ _OWNER_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.\-]{0,63}$")
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class InboundMessage:
53
+ """One message, as any channel adapter normalises it."""
54
+
55
+ channel: str # "feishu", "telegram", "web", ...
56
+ conversation_id: str # the chat / group / room on that channel
57
+ kind: str # "direct" or "group"
58
+ sender_id: str
59
+ text: str
60
+ message_id: str = ""
61
+ mentions_bot: bool = False
62
+ reply_to: str = "" # the message this one answers, if any
63
+ sender_aliases: Tuple[str, ...] = () # other ids for the same person
64
+
65
+ @property
66
+ def key(self) -> str:
67
+ """Unique across channels: two platforms may reuse the same chat id."""
68
+ return f"{self.channel}:{self.conversation_id}"
69
+
70
+ @property
71
+ def sender_ids(self) -> Tuple[str, ...]:
72
+ return tuple(i for i in (self.sender_id, *self.sender_aliases) if i)
73
+
74
+
75
+ def should_respond(msg: InboundMessage) -> bool:
76
+ """Direct chats always; groups only when the bot is addressed."""
77
+ if msg.kind == "direct":
78
+ return True
79
+ return bool(msg.mentions_bot)
80
+
81
+
82
+ def is_admin(msg: InboundMessage, env: Optional[Dict[str, str]] = None) -> bool:
83
+ """ARIA_CHANNEL_ADMINS="feishu:ou_abc,telegram:12345" — per channel, fail closed."""
84
+ raw = (env if env is not None else os.environ).get(ADMINS_ENV, "")
85
+ admins = {item.strip() for item in raw.split(",") if item.strip()}
86
+ return any(f"{msg.channel}:{sid}" in admins for sid in msg.sender_ids)
87
+
88
+
89
+ # ── Admin commands ───────────────────────────────────────────────────────────
90
+
91
+ _OWNER_COMMAND = re.compile(r"^/(?:owner|货主)(?:\s+(\S+))?\s*$", re.IGNORECASE)
92
+ _UNBIND_WORDS = {"off", "clear", "none", "解除", "取消"}
93
+
94
+
95
+ def parse_owner_command(text: str) -> Optional[Tuple[str, str]]:
96
+ """'/owner' → show; '/owner ACME' → bind; '/owner off' → unbind. None otherwise."""
97
+ match = _OWNER_COMMAND.match((text or "").strip())
98
+ if not match:
99
+ return None
100
+ arg = (match.group(1) or "").strip()
101
+ if not arg:
102
+ return ("show", "")
103
+ if arg.lower() in _UNBIND_WORDS:
104
+ return ("unbind", "")
105
+ return ("bind", arg)
106
+
107
+
108
+ def handle_owner_command(msg: InboundMessage, store: "ConversationStore",
109
+ env: Optional[Dict[str, str]] = None) -> Optional[str]:
110
+ """Run an /owner command and return the reply text, or None if it is not one."""
111
+ command = parse_owner_command(msg.text)
112
+ if command is None:
113
+ return None
114
+ action, owner = command
115
+ current = store.owner_for(msg.key)
116
+ if action == "show":
117
+ return (f"本会话绑定的货主:{current}" if current
118
+ else "本会话未绑定货主。管理员可发送 /owner <货主ID> 绑定。")
119
+ if not is_admin(msg, env):
120
+ return "只有管理员可以修改本会话绑定的货主。"
121
+ if msg.kind != "group":
122
+ # A direct chat is one person, not a client; binding it would confine
123
+ # an operator's own chat to one shipper and surprise them later.
124
+ return "货主只能绑定到群聊:一个群对应一个货主。"
125
+ if action == "unbind":
126
+ store.unbind_owner(msg.key)
127
+ return "已解除本会话的货主绑定。"
128
+ if not _OWNER_ID.match(owner):
129
+ return "货主 ID 只能包含字母、数字、下划线、点和短横线,最长 64 个字符。"
130
+ store.bind_owner(msg.key, owner, bound_by=f"{msg.channel}:{msg.sender_id}")
131
+ return f"已绑定:本会话之后的分析只会使用货主 {owner} 的数据。"
132
+
133
+
134
+ # ── Storage ──────────────────────────────────────────────────────────────────
135
+
136
+ def default_db_path() -> Path:
137
+ configured = os.environ.get(DB_ENV, "").strip()
138
+ if configured:
139
+ return Path(configured).expanduser()
140
+ return Path.home() / ".aria" / "conversations.db"
141
+
142
+
143
+ class ConversationStore:
144
+ """Owner bindings and bounded per-conversation history, in one SQLite file."""
145
+
146
+ def __init__(self, path: Optional[Path | str] = None, *, max_turns: int = 20,
147
+ ttl_seconds: float = 7 * 86400, clock: Callable[[], float] = time.time) -> None:
148
+ self.path = Path(path) if path else default_db_path()
149
+ self.path.parent.mkdir(parents=True, exist_ok=True)
150
+ self.max_turns = max(1, int(max_turns))
151
+ self.ttl_seconds = float(ttl_seconds)
152
+ self._clock = clock
153
+ with self._db() as db:
154
+ db.executescript(
155
+ """
156
+ CREATE TABLE IF NOT EXISTS bindings (
157
+ conversation TEXT PRIMARY KEY,
158
+ owner_id TEXT NOT NULL,
159
+ bound_by TEXT NOT NULL,
160
+ bound_at REAL NOT NULL
161
+ );
162
+ CREATE TABLE IF NOT EXISTS history (
163
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
164
+ conversation TEXT NOT NULL,
165
+ role TEXT NOT NULL,
166
+ sender TEXT NOT NULL DEFAULT '',
167
+ content TEXT NOT NULL,
168
+ at REAL NOT NULL
169
+ );
170
+ CREATE INDEX IF NOT EXISTS history_by_conversation ON history (conversation, id);
171
+ """
172
+ )
173
+ try:
174
+ os.chmod(self.path, 0o600) # client messages: owner-readable only
175
+ except OSError:
176
+ pass
177
+
178
+ def _db(self) -> sqlite3.Connection:
179
+ return sqlite3.connect(str(self.path))
180
+
181
+ # bindings
182
+ def bind_owner(self, key: str, owner_id: str, *, bound_by: str) -> None:
183
+ with self._db() as db:
184
+ db.execute(
185
+ "INSERT INTO bindings (conversation, owner_id, bound_by, bound_at) VALUES (?, ?, ?, ?) "
186
+ "ON CONFLICT(conversation) DO UPDATE SET owner_id=excluded.owner_id, "
187
+ "bound_by=excluded.bound_by, bound_at=excluded.bound_at",
188
+ (key, owner_id, bound_by, self._clock()),
189
+ )
190
+
191
+ def unbind_owner(self, key: str) -> None:
192
+ with self._db() as db:
193
+ db.execute("DELETE FROM bindings WHERE conversation = ?", (key,))
194
+
195
+ def bindings(self) -> List[Tuple[str, str]]:
196
+ """Every (conversation key, shipper) binding, for jobs that push to them."""
197
+ with self._db() as db:
198
+ return [tuple(row) for row in db.execute(
199
+ "SELECT conversation, owner_id FROM bindings ORDER BY conversation")]
200
+
201
+ def owner_for(self, key: str) -> Optional[str]:
202
+ with self._db() as db:
203
+ row = db.execute("SELECT owner_id FROM bindings WHERE conversation = ?", (key,)).fetchone()
204
+ return row[0] if row else None
205
+
206
+ # history
207
+ def record(self, key: str, role: str, content: str, *, sender: str = "") -> None:
208
+ if role not in ("user", "assistant") or not (content or "").strip():
209
+ return
210
+ now = self._clock()
211
+ with self._db() as db:
212
+ db.execute("INSERT INTO history (conversation, role, sender, content, at) VALUES (?, ?, ?, ?, ?)",
213
+ (key, role, sender, content, now))
214
+ self._prune(db, key, now)
215
+
216
+ def history(self, key: str) -> List[Dict[str, str]]:
217
+ """Oldest first, in the {role, content} shape model providers take."""
218
+ now = self._clock()
219
+ with self._db() as db:
220
+ self._prune(db, key, now)
221
+ rows = db.execute(
222
+ "SELECT role, content FROM history WHERE conversation = ? ORDER BY id", (key,)
223
+ ).fetchall()
224
+ return [{"role": role, "content": content} for role, content in rows]
225
+
226
+ def forget(self, key: str) -> None:
227
+ with self._db() as db:
228
+ db.execute("DELETE FROM history WHERE conversation = ?", (key,))
229
+
230
+ def _prune(self, db: sqlite3.Connection, key: str, now: float) -> None:
231
+ db.execute("DELETE FROM history WHERE conversation = ? AND at < ?", (key, now - self.ttl_seconds))
232
+ db.execute(
233
+ "DELETE FROM history WHERE conversation = ? AND id NOT IN "
234
+ "(SELECT id FROM history WHERE conversation = ? ORDER BY id DESC LIMIT ?)",
235
+ (key, key, self.max_turns),
236
+ )
237
+
238
+
239
+ # ── Turns ────────────────────────────────────────────────────────────────────
240
+
241
+ @dataclass(frozen=True)
242
+ class ChannelTurn:
243
+ """Everything a model call needs for one message, platform-free."""
244
+
245
+ prompt: str
246
+ history: List[Dict[str, str]] = field(default_factory=list)
247
+ owner_scope: Optional[str] = None
248
+
249
+ @property
250
+ def env(self) -> Dict[str, str]:
251
+ """Environment for a subprocess running this turn's tools."""
252
+ return {OWNER_SCOPE_ENV: self.owner_scope} if self.owner_scope else {}
253
+
254
+ def as_task(self, msg: InboundMessage) -> Dict[str, object]:
255
+ """The same turn as a structured channel task (aria.channel_task.v1)."""
256
+ return {
257
+ "schema": CHANNEL_TASK_SCHEMA,
258
+ "channel": msg.channel,
259
+ "conversation": msg.key,
260
+ "message_id": msg.message_id,
261
+ "prompt": self.prompt,
262
+ "history": list(self.history),
263
+ "owner_scope": self.owner_scope,
264
+ }
265
+
266
+
267
+ def prepare_turn(msg: InboundMessage, store: ConversationStore) -> ChannelTurn:
268
+ """Record the incoming message and build the turn that answers it.
269
+
270
+ History is read before this message is recorded, so the turn carries the
271
+ conversation so far plus the new prompt — not the prompt twice.
272
+ """
273
+ history = store.history(msg.key)
274
+ owner = store.owner_for(msg.key)
275
+ store.record(msg.key, "user", msg.text, sender=msg.sender_id)
276
+ prompt = msg.text
277
+ if owner:
278
+ prompt = (f"[本会话属于货主 {owner}。只分析该货主的数据,回答中不要提及其他货主。]\n"
279
+ f"{msg.text}")
280
+ return ChannelTurn(prompt=prompt, history=history, owner_scope=owner)