aria-code 4.1.3__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 (284) hide show
  1. agents/__init__.py +32 -0
  2. agents/base.py +190 -0
  3. agents/deep/__init__.py +37 -0
  4. agents/deep/calibration_loop.py +144 -0
  5. agents/deep/critic.py +125 -0
  6. agents/deep/deepen.py +193 -0
  7. agents/deep/models.py +149 -0
  8. agents/deep/pipeline.py +164 -0
  9. agents/deep/quant_fusion.py +192 -0
  10. agents/deep/themes.py +95 -0
  11. agents/deep/tiers.py +106 -0
  12. agents/financial/__init__.py +10 -0
  13. agents/financial/catalyst.py +279 -0
  14. agents/financial/debate.py +145 -0
  15. agents/financial/earnings.py +303 -0
  16. agents/financial/fundamental.py +159 -0
  17. agents/financial/macro.py +99 -0
  18. agents/financial/news.py +207 -0
  19. agents/financial/risk.py +132 -0
  20. agents/financial/sector.py +279 -0
  21. agents/financial/synthesis.py +274 -0
  22. agents/financial/technical.py +258 -0
  23. agents/portfolio_agent.py +333 -0
  24. agents/realty/__init__.py +62 -0
  25. agents/realty/asset_diagnosis.py +150 -0
  26. agents/realty/business_match.py +165 -0
  27. agents/realty/cashflow_verify.py +208 -0
  28. agents/realty/contract_rules.py +209 -0
  29. agents/realty/energy_anomaly.py +188 -0
  30. agents/realty/exit_settlement.py +207 -0
  31. agents/realty/fulfillment_risk.py +205 -0
  32. agents/realty/ops_optimize.py +159 -0
  33. agents/realty/revenue_share.py +214 -0
  34. agents/registry.py +144 -0
  35. agents/sports/__init__.py +0 -0
  36. agents/sports/football_agent.py +169 -0
  37. agents/team.py +289 -0
  38. aliyun_data_client.py +660 -0
  39. apps/README.md +12 -0
  40. apps/__init__.py +2 -0
  41. apps/channels/README.md +15 -0
  42. apps/cli/README.md +13 -0
  43. apps/cli/__init__.py +2 -0
  44. apps/cli/bootstrap.py +99 -0
  45. apps/cli/codegen_paths.py +29 -0
  46. apps/cli/commands/__init__.py +16 -0
  47. apps/cli/commands/analysis_cmds.py +288 -0
  48. apps/cli/commands/backtest_cmds.py +1887 -0
  49. apps/cli/commands/broker_cmds.py +1154 -0
  50. apps/cli/commands/business_workflow_cmds.py +289 -0
  51. apps/cli/commands/catalog.py +84 -0
  52. apps/cli/commands/data_cmds.py +405 -0
  53. apps/cli/commands/diagnostic_cmds.py +179 -0
  54. apps/cli/commands/diagnostic_ops_cmds.py +696 -0
  55. apps/cli/commands/finance_render.py +12 -0
  56. apps/cli/commands/market.py +399 -0
  57. apps/cli/commands/market_cmds.py +1276 -0
  58. apps/cli/commands/market_context.py +425 -0
  59. apps/cli/commands/market_render.py +7 -0
  60. apps/cli/commands/model_cmds.py +1579 -0
  61. apps/cli/commands/ops_cmds.py +668 -0
  62. apps/cli/commands/portfolio_cmds.py +962 -0
  63. apps/cli/commands/report.py +377 -0
  64. apps/cli/commands/scaffold_templates.py +617 -0
  65. apps/cli/commands/session_cmds.py +179 -0
  66. apps/cli/commands/session_ux_cmds.py +280 -0
  67. apps/cli/commands/team.py +588 -0
  68. apps/cli/commands/team_render.py +8 -0
  69. apps/cli/commands/ui_cmds.py +358 -0
  70. apps/cli/commands/workflow_cmds.py +279 -0
  71. apps/cli/commands/workspace_cmds.py +1414 -0
  72. apps/cli/config_paths.py +70 -0
  73. apps/cli/config_store.py +61 -0
  74. apps/cli/deterministic.py +122 -0
  75. apps/cli/direct.py +48 -0
  76. apps/cli/github_app_auth.py +135 -0
  77. apps/cli/handlers/__init__.py +11 -0
  78. apps/cli/handlers/broker_handlers.py +122 -0
  79. apps/cli/handlers/chart_handlers.py +1309 -0
  80. apps/cli/handlers/market_handlers.py +2509 -0
  81. apps/cli/handlers/realty_handlers.py +114 -0
  82. apps/cli/handlers/strategy_advice.py +82 -0
  83. apps/cli/hooks.py +180 -0
  84. apps/cli/i18n.py +284 -0
  85. apps/cli/intent.py +136 -0
  86. apps/cli/intent_router.py +217 -0
  87. apps/cli/lifecycle_hooks.py +48 -0
  88. apps/cli/main.py +29 -0
  89. apps/cli/market_metadata.py +135 -0
  90. apps/cli/market_universe.py +265 -0
  91. apps/cli/message_processing.py +257 -0
  92. apps/cli/plan_mode.py +139 -0
  93. apps/cli/plotly_html.py +15 -0
  94. apps/cli/prediction_feedback.py +202 -0
  95. apps/cli/preflight.py +497 -0
  96. apps/cli/project_aria.py +60 -0
  97. apps/cli/prompts/__init__.py +0 -0
  98. apps/cli/prompts/coding.py +658 -0
  99. apps/cli/prompts/system_prompts.py +531 -0
  100. apps/cli/prompts/ui.py +434 -0
  101. apps/cli/providers/__init__.py +1 -0
  102. apps/cli/providers/base.py +271 -0
  103. apps/cli/providers/chat_routing.py +80 -0
  104. apps/cli/providers/llm/__init__.py +1 -0
  105. apps/cli/providers/llm/ollama_stream.py +1170 -0
  106. apps/cli/providers/llm/sse_stream.py +216 -0
  107. apps/cli/providers/runtime_bridge.py +185 -0
  108. apps/cli/runtime_consumer.py +489 -0
  109. apps/cli/session_export.py +87 -0
  110. apps/cli/session_jsonl.py +207 -0
  111. apps/cli/session_store.py +112 -0
  112. apps/cli/todo_tracker.py +190 -0
  113. apps/cli/tools/__init__.py +40 -0
  114. apps/cli/tools/context.py +46 -0
  115. apps/cli/tools/file_tools.py +112 -0
  116. apps/cli/tools/market_tools.py +549 -0
  117. apps/cli/tools/notebook_tools.py +111 -0
  118. apps/cli/tools/system_tools.py +669 -0
  119. apps/cli/tools/write_tools.py +715 -0
  120. apps/cli/tradingview_bridge.py +434 -0
  121. apps/cli/update_check.py +152 -0
  122. apps/cli/utils/__init__.py +0 -0
  123. apps/cli/utils/market_detect.py +1578 -0
  124. apps/daemon/README.md +14 -0
  125. apps/vscode/README.md +115 -0
  126. apps/vscode/package.json +70 -0
  127. aria_cli.py +11636 -0
  128. aria_code-4.1.3.dist-info/METADATA +952 -0
  129. aria_code-4.1.3.dist-info/RECORD +284 -0
  130. aria_code-4.1.3.dist-info/WHEEL +5 -0
  131. aria_code-4.1.3.dist-info/entry_points.txt +2 -0
  132. aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
  133. aria_code-4.1.3.dist-info/top_level.txt +50 -0
  134. aria_daemon.py +1295 -0
  135. aria_feishu_bot.py +1359 -0
  136. aria_relay_client.py +182 -0
  137. aria_relay_server.py +405 -0
  138. aria_telegram_bot.py +202 -0
  139. ariarc.py +328 -0
  140. artifacts.py +491 -0
  141. backtest_report.py +472 -0
  142. brokers/__init__.py +72 -0
  143. brokers/base.py +207 -0
  144. brokers/capabilities.py +264 -0
  145. brokers/cn/__init__.py +10 -0
  146. brokers/cn/easytrader_broker.py +193 -0
  147. brokers/cn/futu_broker.py +194 -0
  148. brokers/cn/longbridge_broker.py +190 -0
  149. brokers/cn/tiger_broker.py +196 -0
  150. brokers/cn/xtquant_broker.py +175 -0
  151. brokers/config.py +364 -0
  152. brokers/intl/__init__.py +5 -0
  153. brokers/intl/alpaca_broker.py +183 -0
  154. brokers/intl/ibkr_broker.py +215 -0
  155. brokers/intl/webull_broker.py +156 -0
  156. brokers/paper_broker.py +259 -0
  157. brokers/planning.py +296 -0
  158. brokers/registry.py +181 -0
  159. brokers/trading.py +237 -0
  160. change_store.py +127 -0
  161. command_safety.py +19 -0
  162. computer_use_tools.py +504 -0
  163. dashboard_generator.py +578 -0
  164. data_analysis_tools.py +808 -0
  165. data_cleaner.py +483 -0
  166. data_service.py +481 -0
  167. datasources/__init__.py +23 -0
  168. datasources/base.py +166 -0
  169. datasources/router.py +221 -0
  170. datasources/sources/__init__.py +15 -0
  171. datasources/sources/akshare_source.py +269 -0
  172. datasources/sources/alpha_vantage_source.py +202 -0
  173. datasources/sources/edgar_source.py +218 -0
  174. datasources/sources/finnhub_source.py +197 -0
  175. datasources/sources/fred_source.py +219 -0
  176. datasources/sources/tushare_source.py +141 -0
  177. datasources/sources/web_scraper_source.py +278 -0
  178. datasources/sources/world_bank_source.py +205 -0
  179. datasources/sources/yfinance_source.py +152 -0
  180. demo_player.py +204 -0
  181. doctor.py +508 -0
  182. file_analysis_tools.py +734 -0
  183. finance_formulas.py +389 -0
  184. football_data_client.py +1670 -0
  185. intent_classifier.py +358 -0
  186. local_finance_tools.py +3221 -0
  187. local_llm_provider.py +552 -0
  188. macro_tools.py +368 -0
  189. market_data_client.py +1899 -0
  190. mcp_client.py +506 -0
  191. memory_manager.py +245 -0
  192. model_capability.py +416 -0
  193. notification_tools.py +248 -0
  194. packages/__init__.py +23 -0
  195. packages/aria_agents/__init__.py +5 -0
  196. packages/aria_agents/manifest.py +69 -0
  197. packages/aria_core/__init__.py +34 -0
  198. packages/aria_core/architecture.py +192 -0
  199. packages/aria_core/export.py +124 -0
  200. packages/aria_core/manifest.py +65 -0
  201. packages/aria_infra/__init__.py +15 -0
  202. packages/aria_infra/arthera.py +52 -0
  203. packages/aria_infra/doctor.py +246 -0
  204. packages/aria_infra/product.py +37 -0
  205. packages/aria_mcp/__init__.py +25 -0
  206. packages/aria_mcp/bridge.py +38 -0
  207. packages/aria_mcp/config.py +97 -0
  208. packages/aria_mcp/tools.py +61 -0
  209. packages/aria_sdk/__init__.py +19 -0
  210. packages/aria_sdk/client.py +396 -0
  211. packages/aria_sdk/providers.py +70 -0
  212. packages/aria_sdk/streaming.py +73 -0
  213. packages/aria_sdk/types.py +86 -0
  214. packages/aria_services/__init__.py +55 -0
  215. packages/aria_services/context.py +258 -0
  216. packages/aria_services/data.py +11 -0
  217. packages/aria_services/provider_health.py +189 -0
  218. packages/aria_services/registry.py +213 -0
  219. packages/aria_services/usage.py +138 -0
  220. packages/aria_skills/__init__.py +5 -0
  221. packages/aria_skills/registry.py +59 -0
  222. packages/aria_tools/__init__.py +5 -0
  223. packages/aria_tools/registry.py +128 -0
  224. packages/quant_engine/__init__.py +6 -0
  225. packages/quant_engine/sports/__init__.py +72 -0
  226. packages/quant_engine/sports/calibrator.py +353 -0
  227. packages/quant_engine/sports/dixon_coles.py +234 -0
  228. packages/quant_engine/sports/elo.py +299 -0
  229. packages/quant_engine/sports/form.py +188 -0
  230. packages/quant_engine/sports/h2h.py +195 -0
  231. packages/quant_engine/sports/ml_model.py +354 -0
  232. packages/quant_engine/sports/predictor.py +311 -0
  233. packages/quant_engine/sports/tracker.py +664 -0
  234. packages/quant_engine/stochastic/__init__.py +27 -0
  235. packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  236. packages/quant_engine/stochastic/ito_calculus.py +477 -0
  237. packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  238. packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  239. packages/quant_engine/stochastic/options_pricing.py +573 -0
  240. packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  241. plan_utils.py +194 -0
  242. plugin_loader.py +328 -0
  243. portfolio_ledger.py +262 -0
  244. privacy/__init__.py +5 -0
  245. privacy/feedback.py +123 -0
  246. project_tools.py +525 -0
  247. providers/__init__.py +30 -0
  248. providers/llm/__init__.py +19 -0
  249. providers/llm/anthropic.py +184 -0
  250. providers/llm/base.py +139 -0
  251. providers/llm/ollama.py +128 -0
  252. providers/llm/openai_compat.py +282 -0
  253. providers/llm/registry.py +358 -0
  254. realty_data_tools.py +659 -0
  255. report_generator.py +1314 -0
  256. runtime/__init__.py +103 -0
  257. runtime/agent_loop.py +1183 -0
  258. runtime/approval.py +51 -0
  259. runtime/events.py +102 -0
  260. runtime/gateway.py +128 -0
  261. runtime/lsp.py +346 -0
  262. runtime/subagent.py +258 -0
  263. runtime/tool_executor.py +104 -0
  264. runtime/tool_policy.py +106 -0
  265. safety/__init__.py +21 -0
  266. safety/permissions.py +275 -0
  267. setup_wizard.py +653 -0
  268. strategy_vault.py +420 -0
  269. ui/__init__.py +100 -0
  270. ui/banner.py +310 -0
  271. ui/completer.py +391 -0
  272. ui/console.py +271 -0
  273. ui/image_render.py +243 -0
  274. ui/input_box.py +376 -0
  275. ui/picker.py +195 -0
  276. ui/render/__init__.py +11 -0
  277. ui/render/finance.py +1480 -0
  278. ui/render/market.py +225 -0
  279. ui/render/output.py +681 -0
  280. ui/render/team.py +346 -0
  281. ui/robot.py +235 -0
  282. workspace/__init__.py +6 -0
  283. workspace/files.py +170 -0
  284. workspace/verify.py +113 -0
notification_tools.py ADDED
@@ -0,0 +1,248 @@
1
+ """
2
+ notification_tools.py — Aria Code push notification dispatcher.
3
+
4
+ Channels (tried in order when configured):
5
+ 1. macOS native notification — always available on macOS, zero-config
6
+ 2. Webhook — 企业微信/飞书/Slack/custom HTTP POST
7
+ 3. Email (SMTP) — opt-in, requires SMTP config
8
+
9
+ Configuration (in ~/.arthera/config.json):
10
+ "notify_macos": true # default true on macOS
11
+ "notify_webhook": "https://..." # webhook URL
12
+ "notify_email": { # optional SMTP
13
+ "smtp_host": "smtp.gmail.com",
14
+ "smtp_port": 587,
15
+ "username": "you@gmail.com",
16
+ "password": "...", # prefer SMTP_PASSWORD env var
17
+ "to": "you@gmail.com"
18
+ }
19
+
20
+ Usage:
21
+ from notification_tools import send_notification
22
+ send_notification("HSBC 触发预警", "现价 USD 79.5 已跌破目标 80.0")
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ import logging
29
+ import os
30
+ import platform
31
+ import smtplib
32
+ import subprocess
33
+ from email.mime.text import MIMEText
34
+ from pathlib import Path
35
+ from typing import Optional
36
+ from urllib import request as _urllib_request
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+ _CONFIG_PATH = Path.home() / ".arthera" / "config.json"
41
+
42
+
43
+ def _load_config() -> dict:
44
+ try:
45
+ if _CONFIG_PATH.exists():
46
+ return json.loads(_CONFIG_PATH.read_text(encoding="utf-8"))
47
+ except Exception:
48
+ pass
49
+ return {}
50
+
51
+
52
+ # ── Channel implementations ──────────────────────────────────────────────────
53
+
54
+ def _notify_macos(title: str, body: str) -> bool:
55
+ """Send a macOS Notification Center alert via osascript."""
56
+ if platform.system() != "Darwin":
57
+ return False
58
+ try:
59
+ script = (
60
+ f'display notification {json.dumps(body)} '
61
+ f'with title {json.dumps(title)} '
62
+ f'subtitle "Aria Code"'
63
+ )
64
+ result = subprocess.run(
65
+ ["osascript", "-e", script],
66
+ capture_output=True, timeout=5,
67
+ )
68
+ return result.returncode == 0
69
+ except Exception as e:
70
+ logger.debug("macOS notification failed: %s", e)
71
+ return False
72
+
73
+
74
+ def _notify_webhook(url: str, title: str, body: str) -> bool:
75
+ """
76
+ POST to a webhook URL. Auto-detects format:
77
+ - 企业微信机器人 (qyapi.weixin.qq.com) → { msgtype: text, text: { content } }
78
+ - 飞书机器人 (open.feishu.cn) → { msg_type: text, content: { text } }
79
+ - Slack (hooks.slack.com) → { text }
80
+ - 钉钉 (oapi.dingtalk.com) → { msgtype: text, text: { content } }
81
+ - 其他 → { title, body }
82
+ """
83
+ try:
84
+ message = f"【{title}】\n{body}"
85
+ if "qyapi.weixin.qq.com" in url:
86
+ payload = {"msgtype": "text", "text": {"content": message}}
87
+ elif "open.feishu.cn" in url or "feishu" in url or "larksuite.com" in url:
88
+ # 飞书交互卡片:带颜色标题 + Markdown 正文
89
+ color = "red" if any(w in title.lower() for w in ("预警", "alert", "错误", "error", "熔断")) else \
90
+ "green" if any(w in title.lower() for w in ("晨报", "brief", "完成", "done")) else "blue"
91
+ payload = {
92
+ "msg_type": "interactive",
93
+ "card": {
94
+ "header": {
95
+ "title": {"tag": "plain_text", "content": title},
96
+ "template": color,
97
+ },
98
+ "elements": [
99
+ {"tag": "div", "text": {"tag": "lark_md", "content": body[:2000]}},
100
+ {"tag": "hr"},
101
+ {"tag": "note", "elements": [
102
+ {"tag": "plain_text", "content": "Aria Code · " + __import__("datetime").datetime.now().strftime("%Y-%m-%d %H:%M")}
103
+ ]},
104
+ ],
105
+ },
106
+ }
107
+ elif "hooks.slack.com" in url:
108
+ payload = {"text": message}
109
+ elif "oapi.dingtalk.com" in url:
110
+ payload = {"msgtype": "text", "text": {"content": message}}
111
+ else:
112
+ payload = {"title": title, "body": body, "text": message}
113
+
114
+ data = json.dumps(payload).encode("utf-8")
115
+ req = _urllib_request.Request(
116
+ url, data=data,
117
+ headers={"Content-Type": "application/json"},
118
+ method="POST",
119
+ )
120
+ with _urllib_request.urlopen(req, timeout=8) as resp:
121
+ return resp.status < 400
122
+ except Exception as e:
123
+ logger.debug("Webhook notification failed (%s): %s", url[:40], e)
124
+ return False
125
+
126
+
127
+ def _notify_telegram(token: str, chat_ids: str, title: str, body: str) -> bool:
128
+ """Send a Telegram message to all allowed chat IDs via Bot API."""
129
+ import urllib.request as _req
130
+ import urllib.parse as _parse
131
+
132
+ ids = [cid.strip() for cid in chat_ids.replace(";", ",").split(",") if cid.strip()]
133
+ if not ids:
134
+ return False
135
+
136
+ text = f"*{title}*\n{body}"
137
+ url = f"https://api.telegram.org/bot{token}/sendMessage"
138
+ success = False
139
+ for chat_id in ids:
140
+ data = _parse.urlencode({
141
+ "chat_id": chat_id,
142
+ "text": text,
143
+ "parse_mode": "Markdown",
144
+ }).encode()
145
+ req = _req.Request(url, data=data, method="POST")
146
+ try:
147
+ with _req.urlopen(req, timeout=8) as resp:
148
+ if resp.status < 400:
149
+ success = True
150
+ except Exception as e:
151
+ logger.debug("Telegram notification failed for chat_id %s: %s", chat_id, e)
152
+ return success
153
+
154
+
155
+ def _notify_email(cfg: dict, title: str, body: str) -> bool:
156
+ """Send a plain-text email via SMTP."""
157
+ try:
158
+ host = cfg.get("smtp_host", "smtp.gmail.com")
159
+ port = int(cfg.get("smtp_port", 587))
160
+ username = cfg.get("username", "")
161
+ password = os.getenv("SMTP_PASSWORD") or cfg.get("password", "")
162
+ to_addr = cfg.get("to", username)
163
+ if not (username and password and to_addr):
164
+ return False
165
+ msg = MIMEText(body, "plain", "utf-8")
166
+ msg["Subject"] = f"[Aria] {title}"
167
+ msg["From"] = username
168
+ msg["To"] = to_addr
169
+ with smtplib.SMTP(host, port, timeout=10) as smtp:
170
+ smtp.starttls()
171
+ smtp.login(username, password)
172
+ smtp.sendmail(username, [to_addr], msg.as_string())
173
+ return True
174
+ except Exception as e:
175
+ logger.debug("Email notification failed: %s", e)
176
+ return False
177
+
178
+
179
+ # ── Public API ────────────────────────────────────────────────────────────────
180
+
181
+ def send_notification(
182
+ title: str,
183
+ body: str,
184
+ *,
185
+ config: Optional[dict] = None,
186
+ ) -> dict:
187
+ """
188
+ Dispatch a notification to all configured channels.
189
+
190
+ Returns a dict with which channels succeeded.
191
+ """
192
+ cfg = config or _load_config()
193
+ results: dict[str, bool] = {}
194
+
195
+ # 1. macOS native (default on when running on macOS)
196
+ if cfg.get("notify_macos", platform.system() == "Darwin"):
197
+ results["macos"] = _notify_macos(title, body)
198
+
199
+ # 2. Telegram Bot
200
+ tg_token = os.getenv("TELEGRAM_BOT_TOKEN") or cfg.get("telegram_bot_token", "")
201
+ tg_chat_ids = os.getenv("TELEGRAM_ALLOWED_IDS") or cfg.get("telegram_chat_ids", "")
202
+ if tg_token and tg_chat_ids and tg_token != "your_bot_token_here":
203
+ results["telegram"] = _notify_telegram(tg_token, tg_chat_ids, title, body)
204
+
205
+ # 3. Webhook (企业微信 / 飞书 / Slack / custom)
206
+ webhook_url = cfg.get("notify_webhook") or os.getenv("ARIA_NOTIFY_WEBHOOK")
207
+ if webhook_url:
208
+ results["webhook"] = _notify_webhook(webhook_url, title, body)
209
+
210
+ # 4. Email
211
+ email_cfg = cfg.get("notify_email")
212
+ if email_cfg and isinstance(email_cfg, dict):
213
+ results["email"] = _notify_email(email_cfg, title, body)
214
+
215
+ if not results:
216
+ logger.debug(
217
+ "No notification channels configured. "
218
+ "Set TELEGRAM_BOT_TOKEN + TELEGRAM_ALLOWED_IDS, "
219
+ "notify_webhook in ~/.arthera/config.json, or ARIA_NOTIFY_WEBHOOK env var."
220
+ )
221
+
222
+ return {
223
+ "sent": any(results.values()),
224
+ "channels": results,
225
+ "title": title,
226
+ "body": body,
227
+ }
228
+
229
+
230
+ def send_alert_notification(alert: dict) -> dict:
231
+ """Convenience wrapper for price alert triggers."""
232
+ sym = alert.get("symbol", "")
233
+ cond = alert.get("condition", "")
234
+ tgt = alert.get("price", "")
235
+ cur = alert.get("triggered_price", "N/A")
236
+ label = alert.get("label") or f"{sym} 价格预警"
237
+
238
+ cond_cn = {
239
+ "gt": f"突破 {tgt}(现价 {cur})",
240
+ "lt": f"跌破 {tgt}(现价 {cur})",
241
+ "cross_up": f"上穿 {tgt}(现价 {cur})",
242
+ "cross_down": f"下穿 {tgt}(现价 {cur})",
243
+ }.get(cond, f"触发条件 {cond} ≈ {tgt},现价 {cur}")
244
+
245
+ return send_notification(
246
+ title=label,
247
+ body=f"{sym} {cond_cn}\n触发时间:{alert.get('triggered_at', '')}",
248
+ )
packages/__init__.py ADDED
@@ -0,0 +1,23 @@
1
+ """Aria Code package facade layer.
2
+
3
+ This namespace is intentionally thin. It exposes stable package boundaries
4
+ without forcing the existing single-file CLI to move all implementation code at
5
+ once.
6
+ """
7
+
8
+ import pathlib as _pathlib
9
+
10
+ # Extend __path__ to include Arthera/packages/ so that
11
+ # `from packages.quant_engine.*` resolves transparently.
12
+ _arthera_pkgs = _pathlib.Path(__file__).parents[2] / "Arthera" / "packages"
13
+ if _arthera_pkgs.exists() and str(_arthera_pkgs) not in __path__:
14
+ __path__.append(str(_arthera_pkgs))
15
+
16
+ __all__ = [
17
+ "aria_agents",
18
+ "aria_core",
19
+ "aria_infra",
20
+ "aria_mcp",
21
+ "aria_skills",
22
+ "aria_tools",
23
+ ]
@@ -0,0 +1,5 @@
1
+ """Agent package facade."""
2
+
3
+ from .manifest import AgentManifest, list_agent_manifests, manifest_from_agent_class
4
+
5
+ __all__ = ["AgentManifest", "list_agent_manifests", "manifest_from_agent_class"]
@@ -0,0 +1,69 @@
1
+ """Agent manifests built from the existing AgentRegistry."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any, Dict, List, Type
7
+
8
+ from agents.base import BaseAgent
9
+ from agents.registry import get_registry
10
+ from packages.aria_core import CapabilityManifest, PermissionLevel, ServiceKind
11
+
12
+
13
+ _CAPABILITIES_BY_AGENT = {
14
+ "technical": ["market.history", "market.technical"],
15
+ "fundamental": ["market.fundamentals", "filings"],
16
+ "macro": ["macro.data", "news"],
17
+ "risk": ["market.history", "portfolio.risk"],
18
+ "news": ["news"],
19
+ "catalyst": ["news", "events"],
20
+ "sector": ["market.sector"],
21
+ "earnings": ["filings", "events"],
22
+ "portfolio": ["portfolio"],
23
+ "synthesis": ["reasoning.synthesis"],
24
+ "debate": ["reasoning.debate"],
25
+ }
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class AgentManifest:
30
+ name: str
31
+ description: str
32
+ builtin: bool
33
+ capabilities: List[str] = field(default_factory=list)
34
+ permissions: List[PermissionLevel] = field(default_factory=lambda: [PermissionLevel.NETWORK])
35
+ output_schema: Dict[str, Any] = field(default_factory=lambda: {
36
+ "type": "object",
37
+ "required": ["agent", "symbol", "analysis", "confidence", "signal"],
38
+ })
39
+
40
+ def manifest(self) -> CapabilityManifest:
41
+ return CapabilityManifest(
42
+ name=self.name,
43
+ kind=ServiceKind.AGENT,
44
+ description=self.description,
45
+ capabilities=self.capabilities,
46
+ permissions=self.permissions,
47
+ output_schema=self.output_schema,
48
+ tags=["agent"],
49
+ )
50
+
51
+
52
+ def manifest_from_agent_class(cls: Type[BaseAgent], *, builtin: bool = True) -> AgentManifest:
53
+ name = getattr(cls, "name", cls.__name__).lower()
54
+ return AgentManifest(
55
+ name=name,
56
+ description=getattr(cls, "description", ""),
57
+ builtin=builtin,
58
+ capabilities=_CAPABILITIES_BY_AGENT.get(name, ["agent"]),
59
+ )
60
+
61
+
62
+ def list_agent_manifests() -> List[AgentManifest]:
63
+ registry = get_registry()
64
+ out: List[AgentManifest] = []
65
+ for item in registry.list():
66
+ cls = registry.get(item["name"])
67
+ if cls:
68
+ out.append(manifest_from_agent_class(cls, builtin=bool(item.get("builtin"))))
69
+ return sorted(out, key=lambda item: item.name)
@@ -0,0 +1,34 @@
1
+ """Core contracts shared by Aria packages."""
2
+
3
+ from .architecture import (
4
+ ARCHITECTURE_SCHEMA_VERSION,
5
+ ArchitectureLayer,
6
+ LayerStatus,
7
+ architecture_contract,
8
+ architecture_gaps,
9
+ architecture_layer_map,
10
+ architecture_status_counts,
11
+ list_architecture_layers,
12
+ required_architecture_layer_names,
13
+ )
14
+ from .export import build_package_manifest, build_session_diagnostic_bundle, write_package_manifest
15
+ from .manifest import CapabilityManifest, PackageLink, PermissionLevel, ServiceKind
16
+
17
+ __all__ = [
18
+ "ARCHITECTURE_SCHEMA_VERSION",
19
+ "ArchitectureLayer",
20
+ "CapabilityManifest",
21
+ "LayerStatus",
22
+ "PackageLink",
23
+ "PermissionLevel",
24
+ "ServiceKind",
25
+ "architecture_contract",
26
+ "architecture_gaps",
27
+ "architecture_layer_map",
28
+ "architecture_status_counts",
29
+ "build_package_manifest",
30
+ "build_session_diagnostic_bundle",
31
+ "list_architecture_layers",
32
+ "required_architecture_layer_names",
33
+ "write_package_manifest",
34
+ ]
@@ -0,0 +1,192 @@
1
+ """Declarative agent architecture contract for Aria Code.
2
+
3
+ The contract keeps Codex/Claude Code style boundaries explicit while the legacy
4
+ CLI is extracted into smaller packages. It is intentionally pure data so tests,
5
+ doctor checks, docs, and future UI views can share one source of truth.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import asdict, dataclass, field
11
+ from enum import Enum
12
+ from typing import Any, Dict, List, Tuple
13
+
14
+
15
+ class LayerStatus(str, Enum):
16
+ DONE = "done"
17
+ PARTIAL = "partial"
18
+ PLANNED = "planned"
19
+ BLOCKED = "blocked"
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class ArchitectureLayer:
24
+ name: str
25
+ responsibility: str
26
+ target_state: str
27
+ current_state: str
28
+ status: LayerStatus
29
+ source_paths: Tuple[str, ...] = field(default_factory=tuple)
30
+ depends_on: Tuple[str, ...] = field(default_factory=tuple)
31
+ next_steps: Tuple[str, ...] = field(default_factory=tuple)
32
+ blockers: Tuple[str, ...] = field(default_factory=tuple)
33
+
34
+ @property
35
+ def is_complete(self) -> bool:
36
+ return self.status == LayerStatus.DONE
37
+
38
+ def to_dict(self) -> Dict[str, Any]:
39
+ data = asdict(self)
40
+ data["status"] = self.status.value
41
+ return data
42
+
43
+
44
+ ARCHITECTURE_SCHEMA_VERSION = "aria.agent-architecture.v1"
45
+
46
+
47
+ _ARCHITECTURE_LAYERS: Tuple[ArchitectureLayer, ...] = (
48
+ ArchitectureLayer(
49
+ name="launcher",
50
+ responsibility="Stable executable entrypoint, runtime selection, and dependency bootstrap.",
51
+ target_state="Shell entrypoints resolve the repo, use a controlled virtualenv, and never depend on the caller's random Python.",
52
+ current_state="aria and aria-code bootstrap local dependencies; existing virtualenvs may still need runtime rebuild support.",
53
+ status=LayerStatus.PARTIAL,
54
+ source_paths=("aria-code", "install.sh"),
55
+ next_steps=("Add a doctor check for Python version drift and a documented venv rebuild command.",),
56
+ ),
57
+ ArchitectureLayer(
58
+ name="settings",
59
+ responsibility="Configuration, secrets, model profiles, and permission policy resolution.",
60
+ target_state="A single settings service resolves env, config files, CLI flags, and secrets without leaking credentials.",
61
+ current_state="Settings are still split across CLI config, env vars, and feature-specific files.",
62
+ status=LayerStatus.PLANNED,
63
+ source_paths=("config.py", "settings_manager.py", "cloud_config.py"),
64
+ next_steps=("Extract SettingsService and make launcher, CLI, daemon, brokers, and MCP use it.",),
65
+ ),
66
+ ArchitectureLayer(
67
+ name="ui",
68
+ responsibility="Terminal rendering, input UX, progress display, and artifact links.",
69
+ target_state="A thin UI adapter renders compact, resumable, non-repetitive output with graceful plain-terminal fallback.",
70
+ current_state="Terminal streaming and approval prompts now have a CLI runtime event consumer; Rich/prompt_toolkit remain optional and generated-file UX still needs hardening.",
71
+ status=LayerStatus.PARTIAL,
72
+ source_paths=("ui/", "apps/cli/commands/", "apps/cli/runtime_consumer.py"),
73
+ next_steps=("Move generated-file open actions and remaining terminal panels behind a UI service.",),
74
+ ),
75
+ ArchitectureLayer(
76
+ name="context",
77
+ responsibility="Conversation memory, context compaction, task continuity, and artifact-backed summaries.",
78
+ target_state="Context is automatically compacted before overflow, with recoverable task state and traceable artifacts.",
79
+ current_state="ContextService owns pressure checks, local compaction, summary prompts, and resume envelopes; durable checkpoints are still pending.",
80
+ status=LayerStatus.PARTIAL,
81
+ source_paths=("packages/aria_services/context.py", "apps/cli/message_processing.py", "apps/cli/commands/session_ux_cmds.py"),
82
+ next_steps=("Persist compaction checkpoints and expose context health through /doctor and support bundles.",),
83
+ blockers=("Choose artifact schema and retention policy for compact/resume checkpoints.",),
84
+ ),
85
+ ArchitectureLayer(
86
+ name="runtime",
87
+ responsibility="Agent turn loop, planning, tool execution, retries, streaming, and interruption handling.",
88
+ target_state="Runtime is separate from UI and business services, with typed tool calls, retries, cancellation, and traces.",
89
+ current_state="A public packages.aria_sdk facade owns SDK-style query/result events, provider selection, streaming normalization, and the reusable runtime tool-turn loop; CLI rendering/approval is consumed through apps.cli.runtime_consumer. The CLI chat turn can now opt into run_agent via apps.cli.providers.runtime_bridge (config use_runtime_loop), with route-aware provider/fallback decisions in apps.cli.providers.chat_routing; send_message falls back to the inline loop on any error.",
90
+ status=LayerStatus.PARTIAL,
91
+ source_paths=("aria_cli.py", "runtime/", "packages/aria_sdk/", "apps/cli/runtime_consumer.py", "apps/cli/deterministic.py", "apps/cli/providers/"),
92
+ depends_on=("settings", "tools", "safety", "context"),
93
+ next_steps=("Verify the use_runtime_loop path in a live REPL, default it on, then retire the inline send_message loop so the turn runs entirely through run_agent.",),
94
+ ),
95
+ ArchitectureLayer(
96
+ name="tools",
97
+ responsibility="Tool registry, schemas, permissions, local commands, and MCP adapters.",
98
+ target_state="All tools are manifests with input schema, permission level, owner service, and deterministic output shape.",
99
+ current_state="Legacy tools can be converted to manifests; some direct command paths still bypass the registry.",
100
+ status=LayerStatus.PARTIAL,
101
+ source_paths=("packages/aria_tools/", "tools/"),
102
+ depends_on=("safety",),
103
+ next_steps=("Route new slash commands through tool/service manifests instead of direct CLI functions.",),
104
+ ),
105
+ ArchitectureLayer(
106
+ name="services",
107
+ responsibility="Product service boundaries for data, reports, brokers, skills, channels, and gateway.",
108
+ target_state="Business logic lives behind services; CLI, daemon, MCP, and webhooks are adapters.",
109
+ current_state="Required service specs now cover gateway, runtime, settings, context, tools, data, reports, brokers, skills, MCP, safety, and observability; several implementations still sit in legacy modules.",
110
+ status=LayerStatus.PARTIAL,
111
+ source_paths=(
112
+ "packages/aria_services/",
113
+ "docs/architecture/service_boundaries.md",
114
+ "docs/architecture/claude_code_parity_gap_analysis.md",
115
+ ),
116
+ depends_on=("settings", "runtime", "tools"),
117
+ next_steps=("Extract concrete SettingsService, SafetyService, ReportService, GatewayService, and ObservabilityService implementations behind the registered service manifests.",),
118
+ ),
119
+ ArchitectureLayer(
120
+ name="mcp",
121
+ responsibility="External package/tool integration through MCP and explicit adapters.",
122
+ target_state="MCP servers expose typed tools, health, permissions, and connection status independent of CLI state.",
123
+ current_state="Arthera Quant Engine bridge has manifests and doctor checks; lifecycle management still needs tightening.",
124
+ status=LayerStatus.PARTIAL,
125
+ source_paths=("packages/aria_mcp/",),
126
+ depends_on=("tools", "services"),
127
+ next_steps=("Add MCP reload/reconnect policy, tool provenance, and per-server failure isolation.",),
128
+ ),
129
+ ArchitectureLayer(
130
+ name="safety",
131
+ responsibility="Filesystem, shell, network, broker, and privacy guardrails.",
132
+ target_state="Every risky action is classified, previewed when needed, audited, and blocked by default for live trading.",
133
+ current_state="Broker preview/confirm exists, but shell/network/file policies are not yet one unified service.",
134
+ status=LayerStatus.PARTIAL,
135
+ source_paths=("safety/", "brokers/", "apps/cli/commands/broker_cmds.py"),
136
+ depends_on=("settings", "tools"),
137
+ next_steps=("Unify command policy, broker risk policy, and privacy controls under SafetyService.",),
138
+ ),
139
+ ArchitectureLayer(
140
+ name="channels",
141
+ responsibility="Daemon, webhook, TradingView alerts, Feishu, Telegram, and future external entrypoints.",
142
+ target_state="Channels submit structured tasks to gateway/runtime and never call CLI internals directly.",
143
+ current_state="TradingView URL/Pine generation exists; webhook-to-daemon routing is still incomplete.",
144
+ status=LayerStatus.PLANNED,
145
+ source_paths=("aria_daemon.py", "apps/channels/"),
146
+ depends_on=("settings", "runtime", "services", "safety"),
147
+ next_steps=("Implement channel registry and TradingView alert webhook flow through gateway.",),
148
+ ),
149
+ ArchitectureLayer(
150
+ name="observability",
151
+ responsibility="Doctor checks, traces, provider health, audit logs, and user-visible diagnostics.",
152
+ target_state="Health checks explain missing services, degraded providers, unsafe configs, and incomplete architecture layers.",
153
+ current_state="Provider and package doctor checks exist; this contract represents architecture coverage. The /architecture command renders it (layers, status, gaps, per-layer next steps; --gaps for outstanding work) and /doctor prints a one-line coverage summary.",
154
+ status=LayerStatus.PARTIAL,
155
+ source_paths=("packages/aria_infra/doctor.py", "packages/aria_services/provider_health.py", "apps/cli/commands/diagnostic_ops_cmds.py", "aria_cli.py"),
156
+ depends_on=("services", "mcp", "safety"),
157
+ next_steps=("Fold the architecture summary into generated support bundles too.",),
158
+ ),
159
+ )
160
+
161
+
162
+ def list_architecture_layers() -> List[ArchitectureLayer]:
163
+ """Return the product architecture layers in dependency order."""
164
+
165
+ return list(_ARCHITECTURE_LAYERS)
166
+
167
+
168
+ def architecture_layer_map() -> Dict[str, ArchitectureLayer]:
169
+ return {layer.name: layer for layer in _ARCHITECTURE_LAYERS}
170
+
171
+
172
+ def required_architecture_layer_names() -> List[str]:
173
+ return [layer.name for layer in _ARCHITECTURE_LAYERS]
174
+
175
+
176
+ def architecture_gaps() -> List[ArchitectureLayer]:
177
+ return [layer for layer in _ARCHITECTURE_LAYERS if not layer.is_complete]
178
+
179
+
180
+ def architecture_status_counts() -> Dict[str, int]:
181
+ counts = {status.value: 0 for status in LayerStatus}
182
+ for layer in _ARCHITECTURE_LAYERS:
183
+ counts[layer.status.value] += 1
184
+ return counts
185
+
186
+
187
+ def architecture_contract() -> Dict[str, Any]:
188
+ return {
189
+ "schema_version": ARCHITECTURE_SCHEMA_VERSION,
190
+ "layers": [layer.to_dict() for layer in _ARCHITECTURE_LAYERS],
191
+ "status_counts": architecture_status_counts(),
192
+ }