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.
- agents/__init__.py +32 -0
- agents/base.py +190 -0
- agents/deep/__init__.py +37 -0
- agents/deep/calibration_loop.py +144 -0
- agents/deep/critic.py +125 -0
- agents/deep/deepen.py +193 -0
- agents/deep/models.py +149 -0
- agents/deep/pipeline.py +164 -0
- agents/deep/quant_fusion.py +192 -0
- agents/deep/themes.py +95 -0
- agents/deep/tiers.py +106 -0
- agents/financial/__init__.py +10 -0
- agents/financial/catalyst.py +279 -0
- agents/financial/debate.py +145 -0
- agents/financial/earnings.py +303 -0
- agents/financial/fundamental.py +159 -0
- agents/financial/macro.py +99 -0
- agents/financial/news.py +207 -0
- agents/financial/risk.py +132 -0
- agents/financial/sector.py +279 -0
- agents/financial/synthesis.py +274 -0
- agents/financial/technical.py +258 -0
- agents/portfolio_agent.py +333 -0
- agents/realty/__init__.py +62 -0
- agents/realty/asset_diagnosis.py +150 -0
- agents/realty/business_match.py +165 -0
- agents/realty/cashflow_verify.py +208 -0
- agents/realty/contract_rules.py +209 -0
- agents/realty/energy_anomaly.py +188 -0
- agents/realty/exit_settlement.py +207 -0
- agents/realty/fulfillment_risk.py +205 -0
- agents/realty/ops_optimize.py +159 -0
- agents/realty/revenue_share.py +214 -0
- agents/registry.py +144 -0
- agents/sports/__init__.py +0 -0
- agents/sports/football_agent.py +169 -0
- agents/team.py +289 -0
- aliyun_data_client.py +660 -0
- apps/README.md +12 -0
- apps/__init__.py +2 -0
- apps/channels/README.md +15 -0
- apps/cli/README.md +13 -0
- apps/cli/__init__.py +2 -0
- apps/cli/bootstrap.py +99 -0
- apps/cli/codegen_paths.py +29 -0
- apps/cli/commands/__init__.py +16 -0
- apps/cli/commands/analysis_cmds.py +288 -0
- apps/cli/commands/backtest_cmds.py +1887 -0
- apps/cli/commands/broker_cmds.py +1154 -0
- apps/cli/commands/business_workflow_cmds.py +289 -0
- apps/cli/commands/catalog.py +84 -0
- apps/cli/commands/data_cmds.py +405 -0
- apps/cli/commands/diagnostic_cmds.py +179 -0
- apps/cli/commands/diagnostic_ops_cmds.py +696 -0
- apps/cli/commands/finance_render.py +12 -0
- apps/cli/commands/market.py +399 -0
- apps/cli/commands/market_cmds.py +1276 -0
- apps/cli/commands/market_context.py +425 -0
- apps/cli/commands/market_render.py +7 -0
- apps/cli/commands/model_cmds.py +1579 -0
- apps/cli/commands/ops_cmds.py +668 -0
- apps/cli/commands/portfolio_cmds.py +962 -0
- apps/cli/commands/report.py +377 -0
- apps/cli/commands/scaffold_templates.py +617 -0
- apps/cli/commands/session_cmds.py +179 -0
- apps/cli/commands/session_ux_cmds.py +280 -0
- apps/cli/commands/team.py +588 -0
- apps/cli/commands/team_render.py +8 -0
- apps/cli/commands/ui_cmds.py +358 -0
- apps/cli/commands/workflow_cmds.py +279 -0
- apps/cli/commands/workspace_cmds.py +1414 -0
- apps/cli/config_paths.py +70 -0
- apps/cli/config_store.py +61 -0
- apps/cli/deterministic.py +122 -0
- apps/cli/direct.py +48 -0
- apps/cli/github_app_auth.py +135 -0
- apps/cli/handlers/__init__.py +11 -0
- apps/cli/handlers/broker_handlers.py +122 -0
- apps/cli/handlers/chart_handlers.py +1309 -0
- apps/cli/handlers/market_handlers.py +2509 -0
- apps/cli/handlers/realty_handlers.py +114 -0
- apps/cli/handlers/strategy_advice.py +82 -0
- apps/cli/hooks.py +180 -0
- apps/cli/i18n.py +284 -0
- apps/cli/intent.py +136 -0
- apps/cli/intent_router.py +217 -0
- apps/cli/lifecycle_hooks.py +48 -0
- apps/cli/main.py +29 -0
- apps/cli/market_metadata.py +135 -0
- apps/cli/market_universe.py +265 -0
- apps/cli/message_processing.py +257 -0
- apps/cli/plan_mode.py +139 -0
- apps/cli/plotly_html.py +15 -0
- apps/cli/prediction_feedback.py +202 -0
- apps/cli/preflight.py +497 -0
- apps/cli/project_aria.py +60 -0
- apps/cli/prompts/__init__.py +0 -0
- apps/cli/prompts/coding.py +658 -0
- apps/cli/prompts/system_prompts.py +531 -0
- apps/cli/prompts/ui.py +434 -0
- apps/cli/providers/__init__.py +1 -0
- apps/cli/providers/base.py +271 -0
- apps/cli/providers/chat_routing.py +80 -0
- apps/cli/providers/llm/__init__.py +1 -0
- apps/cli/providers/llm/ollama_stream.py +1170 -0
- apps/cli/providers/llm/sse_stream.py +216 -0
- apps/cli/providers/runtime_bridge.py +185 -0
- apps/cli/runtime_consumer.py +489 -0
- apps/cli/session_export.py +87 -0
- apps/cli/session_jsonl.py +207 -0
- apps/cli/session_store.py +112 -0
- apps/cli/todo_tracker.py +190 -0
- apps/cli/tools/__init__.py +40 -0
- apps/cli/tools/context.py +46 -0
- apps/cli/tools/file_tools.py +112 -0
- apps/cli/tools/market_tools.py +549 -0
- apps/cli/tools/notebook_tools.py +111 -0
- apps/cli/tools/system_tools.py +669 -0
- apps/cli/tools/write_tools.py +715 -0
- apps/cli/tradingview_bridge.py +434 -0
- apps/cli/update_check.py +152 -0
- apps/cli/utils/__init__.py +0 -0
- apps/cli/utils/market_detect.py +1578 -0
- apps/daemon/README.md +14 -0
- apps/vscode/README.md +115 -0
- apps/vscode/package.json +70 -0
- aria_cli.py +11636 -0
- aria_code-4.1.3.dist-info/METADATA +952 -0
- aria_code-4.1.3.dist-info/RECORD +284 -0
- aria_code-4.1.3.dist-info/WHEEL +5 -0
- aria_code-4.1.3.dist-info/entry_points.txt +2 -0
- aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
- aria_code-4.1.3.dist-info/top_level.txt +50 -0
- aria_daemon.py +1295 -0
- aria_feishu_bot.py +1359 -0
- aria_relay_client.py +182 -0
- aria_relay_server.py +405 -0
- aria_telegram_bot.py +202 -0
- ariarc.py +328 -0
- artifacts.py +491 -0
- backtest_report.py +472 -0
- brokers/__init__.py +72 -0
- brokers/base.py +207 -0
- brokers/capabilities.py +264 -0
- brokers/cn/__init__.py +10 -0
- brokers/cn/easytrader_broker.py +193 -0
- brokers/cn/futu_broker.py +194 -0
- brokers/cn/longbridge_broker.py +190 -0
- brokers/cn/tiger_broker.py +196 -0
- brokers/cn/xtquant_broker.py +175 -0
- brokers/config.py +364 -0
- brokers/intl/__init__.py +5 -0
- brokers/intl/alpaca_broker.py +183 -0
- brokers/intl/ibkr_broker.py +215 -0
- brokers/intl/webull_broker.py +156 -0
- brokers/paper_broker.py +259 -0
- brokers/planning.py +296 -0
- brokers/registry.py +181 -0
- brokers/trading.py +237 -0
- change_store.py +127 -0
- command_safety.py +19 -0
- computer_use_tools.py +504 -0
- dashboard_generator.py +578 -0
- data_analysis_tools.py +808 -0
- data_cleaner.py +483 -0
- data_service.py +481 -0
- datasources/__init__.py +23 -0
- datasources/base.py +166 -0
- datasources/router.py +221 -0
- datasources/sources/__init__.py +15 -0
- datasources/sources/akshare_source.py +269 -0
- datasources/sources/alpha_vantage_source.py +202 -0
- datasources/sources/edgar_source.py +218 -0
- datasources/sources/finnhub_source.py +197 -0
- datasources/sources/fred_source.py +219 -0
- datasources/sources/tushare_source.py +141 -0
- datasources/sources/web_scraper_source.py +278 -0
- datasources/sources/world_bank_source.py +205 -0
- datasources/sources/yfinance_source.py +152 -0
- demo_player.py +204 -0
- doctor.py +508 -0
- file_analysis_tools.py +734 -0
- finance_formulas.py +389 -0
- football_data_client.py +1670 -0
- intent_classifier.py +358 -0
- local_finance_tools.py +3221 -0
- local_llm_provider.py +552 -0
- macro_tools.py +368 -0
- market_data_client.py +1899 -0
- mcp_client.py +506 -0
- memory_manager.py +245 -0
- model_capability.py +416 -0
- notification_tools.py +248 -0
- packages/__init__.py +23 -0
- packages/aria_agents/__init__.py +5 -0
- packages/aria_agents/manifest.py +69 -0
- packages/aria_core/__init__.py +34 -0
- packages/aria_core/architecture.py +192 -0
- packages/aria_core/export.py +124 -0
- packages/aria_core/manifest.py +65 -0
- packages/aria_infra/__init__.py +15 -0
- packages/aria_infra/arthera.py +52 -0
- packages/aria_infra/doctor.py +246 -0
- packages/aria_infra/product.py +37 -0
- packages/aria_mcp/__init__.py +25 -0
- packages/aria_mcp/bridge.py +38 -0
- packages/aria_mcp/config.py +97 -0
- packages/aria_mcp/tools.py +61 -0
- packages/aria_sdk/__init__.py +19 -0
- packages/aria_sdk/client.py +396 -0
- packages/aria_sdk/providers.py +70 -0
- packages/aria_sdk/streaming.py +73 -0
- packages/aria_sdk/types.py +86 -0
- packages/aria_services/__init__.py +55 -0
- packages/aria_services/context.py +258 -0
- packages/aria_services/data.py +11 -0
- packages/aria_services/provider_health.py +189 -0
- packages/aria_services/registry.py +213 -0
- packages/aria_services/usage.py +138 -0
- packages/aria_skills/__init__.py +5 -0
- packages/aria_skills/registry.py +59 -0
- packages/aria_tools/__init__.py +5 -0
- packages/aria_tools/registry.py +128 -0
- packages/quant_engine/__init__.py +6 -0
- packages/quant_engine/sports/__init__.py +72 -0
- packages/quant_engine/sports/calibrator.py +353 -0
- packages/quant_engine/sports/dixon_coles.py +234 -0
- packages/quant_engine/sports/elo.py +299 -0
- packages/quant_engine/sports/form.py +188 -0
- packages/quant_engine/sports/h2h.py +195 -0
- packages/quant_engine/sports/ml_model.py +354 -0
- packages/quant_engine/sports/predictor.py +311 -0
- packages/quant_engine/sports/tracker.py +664 -0
- packages/quant_engine/stochastic/__init__.py +27 -0
- packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- packages/quant_engine/stochastic/ito_calculus.py +477 -0
- packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- packages/quant_engine/stochastic/options_pricing.py +573 -0
- packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- plan_utils.py +194 -0
- plugin_loader.py +328 -0
- portfolio_ledger.py +262 -0
- privacy/__init__.py +5 -0
- privacy/feedback.py +123 -0
- project_tools.py +525 -0
- providers/__init__.py +30 -0
- providers/llm/__init__.py +19 -0
- providers/llm/anthropic.py +184 -0
- providers/llm/base.py +139 -0
- providers/llm/ollama.py +128 -0
- providers/llm/openai_compat.py +282 -0
- providers/llm/registry.py +358 -0
- realty_data_tools.py +659 -0
- report_generator.py +1314 -0
- runtime/__init__.py +103 -0
- runtime/agent_loop.py +1183 -0
- runtime/approval.py +51 -0
- runtime/events.py +102 -0
- runtime/gateway.py +128 -0
- runtime/lsp.py +346 -0
- runtime/subagent.py +258 -0
- runtime/tool_executor.py +104 -0
- runtime/tool_policy.py +106 -0
- safety/__init__.py +21 -0
- safety/permissions.py +275 -0
- setup_wizard.py +653 -0
- strategy_vault.py +420 -0
- ui/__init__.py +100 -0
- ui/banner.py +310 -0
- ui/completer.py +391 -0
- ui/console.py +271 -0
- ui/image_render.py +243 -0
- ui/input_box.py +376 -0
- ui/picker.py +195 -0
- ui/render/__init__.py +11 -0
- ui/render/finance.py +1480 -0
- ui/render/market.py +225 -0
- ui/render/output.py +681 -0
- ui/render/team.py +346 -0
- ui/robot.py +235 -0
- workspace/__init__.py +6 -0
- workspace/files.py +170 -0
- 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,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
|
+
}
|