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
ui/render/team.py ADDED
@@ -0,0 +1,346 @@
1
+ """Rendering helpers for /team output — table, verdict banner, adaptive widths."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ from dataclasses import dataclass
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ if TYPE_CHECKING:
10
+ from rich.console import Console
11
+
12
+ # ── Signal → Rich style ──────────────────────────────────────────────────────
13
+
14
+ SIGNAL_COLORS: dict[str, str] = {
15
+ "BUY": "green",
16
+ "STRONG_BUY": "bold green",
17
+ "SELL": "red",
18
+ "STRONG_SELL": "bold red",
19
+ "HOLD": "yellow",
20
+ }
21
+
22
+ # ── Verdict banner ────────────────────────────────────────────────────────────
23
+
24
+ VERDICT_STYLE: dict[str, tuple[str, str]] = {
25
+ "HEALTHY": ("green", "✅"),
26
+ "NEEDS_ATTENTION": ("yellow", "⚠️ "),
27
+ "HIGH_RISK": ("red", "🔴"),
28
+ "STRONG_BUY": ("bold green", "▲▲"),
29
+ "BUY": ("green", "▲ "),
30
+ "HOLD": ("dim", "─ "),
31
+ "SELL": ("red", "▼ "),
32
+ "STRONG_SELL": ("bold red", "▼▼"),
33
+ }
34
+
35
+
36
+ # ── Streaming agent tree (Claude Code-style nested ⏺ rendering) ────────────────
37
+
38
+ AGENT_LABELS: dict[str, str] = {
39
+ "fundamental": "基本面", "technical": "技术面", "macro": "宏观",
40
+ "risk": "风险", "news": "新闻", "catalyst": "催化剂",
41
+ "sector": "行业", "sentiment": "情绪", "debate": "分歧调解",
42
+ "valuation": "估值", "quant": "量化", "synthesis": "综合",
43
+ }
44
+
45
+
46
+ def agent_label(name: str) -> str:
47
+ return AGENT_LABELS.get((name or "").lower(), name or "?")
48
+
49
+
50
+ def render_agent_tree_root(console, sym: str, n_agents: int, lang: str = "zh") -> None:
51
+ """Print the root of the agent tree: ⏺ 多代理分析 SYM N 个分析师并行"""
52
+ head = "多代理分析" if lang == "zh" else "Multi-agent analysis"
53
+ sub = (f"{n_agents} 个分析师并行" if lang == "zh"
54
+ else f"{n_agents} analysts in parallel")
55
+ console.print(f"\n [#C08050]⏺[/#C08050] [bold]{head} {sym}[/bold] [dim]{sub}[/dim]")
56
+
57
+
58
+ def render_agent_node(console, name: str, signal: str | None,
59
+ key_point: str | None, success: bool = True,
60
+ error: str | None = None) -> None:
61
+ """Print one completed-agent leaf: ⎿ ⏺ 基本面 BUY ROE 24%·PE 32 偏高"""
62
+ label = agent_label(name)
63
+ if not success or error:
64
+ _err_label = {
65
+ "timeout": "超时", "rate_limited": "数据源限流",
66
+ "no_data": "无数据", "": "失败",
67
+ }.get((error or "").lower(), error or "失败")
68
+ console.print(f" [dim]⎿ ⏺ {label} {_err_label}[/dim]")
69
+ return
70
+ sig = (signal or "").upper()
71
+ color = SIGNAL_COLORS.get(sig, "dim")
72
+ sig_disp = f"[{color}]{sig}[/{color}] " if sig else ""
73
+ kp = (key_point or "").strip().replace("\n", " ")
74
+ if len(kp) > 52:
75
+ kp = kp[:52] + "…"
76
+ console.print(
77
+ f" [dim]⎿[/dim] [#C08050]⏺[/#C08050] [bold]{label}[/bold] "
78
+ f"{sig_disp}[dim]{kp}[/dim]"
79
+ )
80
+
81
+
82
+ def render_agent_synthesis_leaf(console, signal: str | None,
83
+ confidence: float | None, elapsed: float | None,
84
+ lang: str = "zh") -> None:
85
+ """Print the synthesis leaf: ⎿ 综合: ▲ BUY (置信 68%) 耗时 4.2s"""
86
+ sig = (signal or "").upper()
87
+ color, icon = VERDICT_STYLE.get(sig, ("dim", "●"))
88
+ conf = (f" [dim]置信 {confidence:.0%}[/dim]" if confidence else "")
89
+ el = (f" [dim]耗时 {elapsed:.1f}s[/dim]" if elapsed else "")
90
+ lab = "综合" if lang == "zh" else "Synthesis"
91
+ console.print(
92
+ f" [dim]⎿[/dim] [bold]{lab}[/bold] [{color}]{icon} {sig}[/{color}]{conf}{el}"
93
+ )
94
+
95
+
96
+ def build_verdict_body(
97
+ verdict: str,
98
+ subtitle: str = "",
99
+ confidence: float | None = None,
100
+ ) -> str:
101
+ """Return a Rich markup string suitable for a Panel body."""
102
+ verdict_upper = verdict.upper()
103
+ style, icon = VERDICT_STYLE.get(verdict_upper, ("dim", "●"))
104
+ conf_str = f" [dim]置信度 {confidence:.0%}[/dim]" if confidence else ""
105
+ body = f"[{style}]{icon} {verdict_upper}[/{style}]{conf_str}"
106
+ if subtitle:
107
+ body += f"\n[dim]{subtitle}[/dim]"
108
+ return body
109
+
110
+
111
+ def render_verdict_banner(
112
+ verdict: str,
113
+ subtitle: str = "",
114
+ confidence: float | None = None,
115
+ *,
116
+ console: "Console | None" = None,
117
+ has_rich: bool = True,
118
+ ) -> None:
119
+ """Print a visually prominent verdict/signal result.
120
+
121
+ Falls back to a plain print when Rich is unavailable or *console* is None.
122
+ """
123
+ if not verdict:
124
+ return
125
+ verdict_upper = verdict.upper()
126
+
127
+ if not has_rich or console is None:
128
+ conf_str = f" ({confidence:.0%})" if confidence else ""
129
+ sub_str = f" {subtitle}" if subtitle else ""
130
+ print(f"\n {verdict_upper}{conf_str}{sub_str}\n")
131
+ return
132
+
133
+ try:
134
+ from rich import box as _rbox
135
+ from rich.panel import Panel
136
+ body = build_verdict_body(verdict_upper, subtitle, confidence)
137
+ console.print(Panel(body, box=_rbox.SIMPLE, padding=(0, 2)))
138
+ except Exception:
139
+ conf_str = f" ({confidence:.0%})" if confidence else ""
140
+ console.print(f"\n {verdict_upper}{conf_str}\n")
141
+
142
+
143
+ # ── Team table ────────────────────────────────────────────────────────────────
144
+
145
+ @dataclass(frozen=True)
146
+ class TeamTableRow:
147
+ agent: str
148
+ signal: str
149
+ confidence: str
150
+ key_point: str
151
+ success: bool
152
+ signal_color: str = "dim"
153
+ is_debate: bool = False
154
+
155
+
156
+ # Minimum usable terminal width before we drop the key_point column entirely.
157
+ _KEY_POINT_MIN_TERMINAL = 72
158
+
159
+
160
+ def calc_column_widths(terminal_width: int) -> tuple[int, int, int, int]:
161
+ """Return ``(agent_w, signal_w, conf_w, key_w)`` adaptive to *terminal_width*.
162
+
163
+ Column budget (chars used by content only, excluding Rich padding/borders):
164
+
165
+ +-----------+---------+-----------+--------------------+
166
+ | Agent | Signal | Conf | Key point |
167
+ +-----------+---------+-----------+--------------------+
168
+ Fixed overhead per row: ~7 chars (borders + padding × 4 cols).
169
+
170
+ Width tiers
171
+ -----------
172
+ ≥ 120 cols full layout 14 | 10 | 7 | 38
173
+ ≥ 100 cols medium 12 | 9 | 6 | 30
174
+ ≥ 80 cols compact 10 | 8 | 6 | 20
175
+ ≥ 72 cols tight 9 | 8 | 6 | 12
176
+ < 72 cols no key col 9 | 8 | 6 | 0 (column omitted)
177
+ """
178
+ if terminal_width >= 120:
179
+ return (14, 10, 7, 38)
180
+ if terminal_width >= 100:
181
+ return (12, 9, 6, 30)
182
+ if terminal_width >= 80:
183
+ return (10, 8, 6, 20)
184
+ if terminal_width >= _KEY_POINT_MIN_TERMINAL:
185
+ return ( 9, 8, 6, 12)
186
+ return (9, 8, 6, 0)
187
+
188
+
189
+ def truncate_cell(value: Any, width: int = 36) -> str:
190
+ text = str(value or "").strip().replace("\n", " ")
191
+ if width <= 0:
192
+ return ""
193
+ if width <= 1:
194
+ return text[:width]
195
+ if len(text) <= width:
196
+ return text
197
+ return text[: width - 1].rstrip() + "…"
198
+
199
+
200
+ def team_mode_label(results: list[Any], use_full: bool = False) -> str:
201
+ non_debate = [r for r in results if getattr(r, "agent", None) != "debate"]
202
+ return f"{len(non_debate)}-agent {'完整分析' if use_full else '分析'}"
203
+
204
+
205
+ _AGENT_DISPLAY = {
206
+ "fundamental": "fundmntl",
207
+ "synthesis": "synthsis",
208
+ "catalyst": "catalyst",
209
+ }
210
+
211
+
212
+ def build_team_table_rows(results: list[Any], key_width: int = 36) -> list[TeamTableRow]:
213
+ rows: list[TeamTableRow] = []
214
+ debate_rows: list[TeamTableRow] = []
215
+
216
+ for result in results:
217
+ agent = str(getattr(result, "agent", "") or "")
218
+ agent = _AGENT_DISPLAY.get(agent, agent)
219
+ success = bool(getattr(result, "success", False))
220
+ raw_signal = str(getattr(result, "signal", "") or "")
221
+ signal = raw_signal or "N/A"
222
+ conf_val = float(getattr(result, "confidence", 0.0) or 0.0)
223
+ confidence = f"{conf_val:.0%}" if success else "-"
224
+ color = SIGNAL_COLORS.get(raw_signal.upper(), "dim")
225
+
226
+ if agent == "debate":
227
+ debate_rows.append(TeamTableRow(
228
+ agent="debate",
229
+ signal=raw_signal or "ADJ",
230
+ confidence=confidence,
231
+ key_point="信号分歧调解",
232
+ success=success,
233
+ signal_color="orange1",
234
+ is_debate=True,
235
+ ))
236
+ continue
237
+
238
+ if success:
239
+ kpts = getattr(result, "key_points", []) or []
240
+ key_point = truncate_cell(kpts[0] if kpts else "", key_width)
241
+ else:
242
+ _raw_err = getattr(result, "error", None) or "failed"
243
+ _err_display = {
244
+ "stale_or_conflicting_price": "数据冲突 — 价格与行情不符",
245
+ "timeout": "分析超时",
246
+ "no_data": "数据不可用",
247
+ "agent_failed": "Agent 执行失败",
248
+ }.get(_raw_err, _raw_err)
249
+ key_point = truncate_cell(_err_display, key_width)
250
+
251
+ rows.append(TeamTableRow(
252
+ agent=agent,
253
+ signal=signal,
254
+ confidence=confidence,
255
+ key_point=key_point,
256
+ success=success,
257
+ signal_color=color,
258
+ ))
259
+
260
+ return rows + debate_rows
261
+
262
+
263
+ def render_team_rows_plain(rows: list[TeamTableRow]) -> list[str]:
264
+ """Plain-text fallback (no Rich)."""
265
+ lines: list[str] = []
266
+ for row in rows:
267
+ icon = "OK" if row.success else "WARN"
268
+ lines.append(
269
+ f" {icon} [{row.agent}] {row.signal} ({row.confidence}) {row.key_point}".rstrip()
270
+ )
271
+ return lines
272
+
273
+
274
+ def render_team_table(
275
+ sym: str,
276
+ rows: list[TeamTableRow],
277
+ use_full: bool = False,
278
+ *,
279
+ console: "Console | None" = None,
280
+ terminal_width: int | None = None,
281
+ has_rich: bool = True,
282
+ ) -> None:
283
+ """Print the /team results table, adapting column widths to *terminal_width*.
284
+
285
+ Falls back to :func:`render_team_rows_plain` when Rich is unavailable.
286
+ """
287
+ if not has_rich or console is None:
288
+ for line in render_team_rows_plain(rows):
289
+ print(line)
290
+ return
291
+
292
+ try:
293
+ from rich import box as _rbox
294
+ from rich.table import Table as _Table
295
+ except ImportError:
296
+ for line in render_team_rows_plain(rows):
297
+ console.print(line)
298
+ return
299
+
300
+ # Determine terminal width from console if not supplied
301
+ if terminal_width is None:
302
+ terminal_width = getattr(console, "width", None) or shutil.get_terminal_size().columns
303
+
304
+ agent_w, signal_w, conf_w, key_w = calc_column_widths(terminal_width)
305
+ mode_label = team_mode_label(rows, use_full) # rows already built; agent count approximate
306
+
307
+ tbl = _Table(
308
+ title=f"[bold]/team {sym}[/bold] · [dim]{mode_label}[/dim]",
309
+ box=_rbox.ROUNDED,
310
+ border_style="dim",
311
+ show_header=True,
312
+ header_style="bold dim",
313
+ padding=(0, 1),
314
+ )
315
+ tbl.add_column("Agent", width=agent_w)
316
+ tbl.add_column("信号", width=signal_w)
317
+ tbl.add_column("置信度", justify="right", width=conf_w)
318
+ if key_w > 0:
319
+ tbl.add_column("关键点", width=key_w)
320
+
321
+ for row in rows:
322
+ # Re-truncate key_point to the current adaptive width
323
+ kp = truncate_cell(row.key_point, key_w) if key_w > 0 else None
324
+
325
+ agent_cell = (
326
+ f"[orange1]{row.agent}[/orange1]"
327
+ if row.is_debate
328
+ else f"[dim]{row.agent}[/dim]"
329
+ )
330
+ key_cell = (
331
+ "[dim][orange1]信号分歧调解[/orange1][/dim]"
332
+ if row.is_debate
333
+ else f"[dim]{kp}[/dim]"
334
+ ) if key_w > 0 else None
335
+
336
+ cells = [
337
+ agent_cell,
338
+ f"[{row.signal_color}]{row.signal}[/{row.signal_color}]",
339
+ f"[dim]{row.confidence}[/dim]" if row.success else "[dim]—[/dim]",
340
+ ]
341
+ if key_cell is not None:
342
+ cells.append(key_cell)
343
+ tbl.add_row(*cells)
344
+
345
+ console.print()
346
+ console.print(tbl)
ui/robot.py ADDED
@@ -0,0 +1,235 @@
1
+ """Aria robot mascot — animated terminal character.
2
+
3
+ States
4
+ ------
5
+ The mascot stays visually stable at startup. Runtime state is shown by the
6
+ compact status dot so the banner keeps the same low-noise feel as Claude Code.
7
+
8
+ Robot shape (7 rows, 13 columns, hand-placed). It is intentionally drawn as a
9
+ terminal icon, not a raster-image conversion: light shell, dark screen, one
10
+ white eye, one copper eye, a copper status strip, and four small legs.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import os
16
+ import sys
17
+ import threading
18
+ import time
19
+ from enum import Enum
20
+
21
+
22
+ class RobotState(Enum):
23
+ IDLE = "idle"
24
+ THINKING = "thinking"
25
+ STREAMING = "streaming"
26
+ ERROR = "error"
27
+ DONE = "done"
28
+
29
+
30
+ # ── Shared mutable state (written by aria_cli, read by input_box) ─────────────
31
+ _state = RobotState.IDLE
32
+ _state_lock = threading.Lock()
33
+ _done_at: float | None = None # timestamp when DONE state was set
34
+
35
+
36
+ def set_robot_state(state: RobotState) -> None:
37
+ global _state, _done_at
38
+ with _state_lock:
39
+ _state = state
40
+ _done_at = time.monotonic() if state is RobotState.DONE else None
41
+
42
+
43
+ def get_robot_state() -> RobotState:
44
+ with _state_lock:
45
+ # Auto-revert DONE → IDLE after 1.5 s
46
+ if _state is RobotState.DONE and _done_at is not None:
47
+ if time.monotonic() - _done_at > 1.5:
48
+ return RobotState.IDLE
49
+ return _state
50
+
51
+
52
+ # Eye symbols per state. IDLE mirrors the mascot art: one light square eye and
53
+ # one copper dash eye.
54
+ _EYES = {
55
+ RobotState.IDLE: ("■", "▬"),
56
+ RobotState.THINKING: ("◐", "◑"),
57
+ RobotState.STREAMING: ("▸", "▸"),
58
+ RobotState.ERROR: ("×", "×"),
59
+ RobotState.DONE: ("✓", "✓"),
60
+ }
61
+
62
+ # Accent colour per state
63
+ _COLOUR = {
64
+ RobotState.IDLE: "#C08050",
65
+ RobotState.THINKING: "#d29922",
66
+ RobotState.STREAMING: "#3fb950",
67
+ RobotState.ERROR: "#f85149",
68
+ RobotState.DONE: "#3fb950",
69
+ }
70
+
71
+ # Status text per state (used by status bar dot)
72
+ _STATUS = {
73
+ RobotState.IDLE: "",
74
+ RobotState.THINKING: "thinking…",
75
+ RobotState.STREAMING: "generating…",
76
+ RobotState.ERROR: "error",
77
+ RobotState.DONE: "done",
78
+ }
79
+
80
+ # ── Theme-aware palette ───────────────────────────────────────────────────────
81
+ # Every region is a BACKGROUND fill — a space painted with an `on <colour>` style.
82
+ # The terminal paints a cell background across the whole line height (including the
83
+ # inter-row gap), so fills join into a solid shape with no horizontal striping.
84
+ # Two palettes (light/dark) are swapped from the OS/terminal theme so the robot
85
+ # inverts (white-on-dark ↔ dark-on-light) and never disappears into the background.
86
+ # The copper accent reads on both, so it is shared (just darkened a touch on light).
87
+ _PALETTES = {
88
+ "dark": {
89
+ "shelltop": "#e8e2d4", # thin top cap (▄): fg only → transparent above
90
+ "shell": "on #e8e2d4", # light shell body
91
+ "screen": "on #0d1117", # dark screen
92
+ "eye": "#f6f2ea on #0d1117", # light square eye (▀)
93
+ "dash": "#C08050 on #0d1117", # copper dash (▬)
94
+ "ear": "#C08050 on #9d9488", # copper dot on a gray ear nub (▪)
95
+ "strip": "#C08050 on #e8e2d4", # copper strip on the body bottom (▬)
96
+ "leg": "#8a8176", # gray legs (▀)
97
+ },
98
+ "light": {
99
+ "shelltop": "#E7E1D3", # warm cap, matching the light shell
100
+ "shell": "on #E7E1D3", # warm shell, distinct from white terminal bg
101
+ "screen": "on #0D1117", # same dark screen as dark mode
102
+ "eye": "#F6F2EA on #0D1117", # light eye on dark screen
103
+ "dash": "#9A6700 on #0D1117", # dark copper on dark screen
104
+ "ear": "#9A6700 on #6E7781", # copper dot on gray side nub
105
+ "strip": "#9A6700 on #E7E1D3", # copper strip on warm shell
106
+ "leg": "#6E7781", # medium gray legs
107
+ },
108
+ }
109
+
110
+ _theme_cache: str | None = None
111
+
112
+
113
+ def _resolve_theme() -> str:
114
+ pref = os.environ.get("ARIA_THEME", "").strip().lower()
115
+ if pref in ("light", "dark"):
116
+ return pref
117
+ if sys.platform == "darwin":
118
+ try:
119
+ import subprocess
120
+ r = subprocess.run(
121
+ ["defaults", "read", "-g", "AppleInterfaceStyle"],
122
+ capture_output=True, text=True, timeout=1,
123
+ )
124
+ # `defaults` prints "Dark" in dark mode and errors (non-zero) in light.
125
+ return "dark" if (r.returncode == 0 and "dark" in r.stdout.lower()) else "light"
126
+ except Exception:
127
+ pass
128
+ # xterm-style COLORFGBG ("fg;bg"): bg 0-6/8 → dark, 7/9-15 → light.
129
+ cfb = os.environ.get("COLORFGBG", "")
130
+ if ";" in cfb:
131
+ try:
132
+ bg = int(cfb.split(";")[-1])
133
+ return "light" if (bg == 7 or bg >= 9) else "dark"
134
+ except Exception:
135
+ pass
136
+ return "dark"
137
+
138
+
139
+ def detect_theme() -> str:
140
+ """Return 'light' or 'dark' for the mascot, auto-detected once and cached.
141
+
142
+ Order: ``ARIA_THEME`` env override → macOS system appearance → ``COLORFGBG``
143
+ → default dark.
144
+ """
145
+ global _theme_cache
146
+ if _theme_cache is None:
147
+ _theme_cache = _resolve_theme()
148
+ return _theme_cache
149
+
150
+
151
+ # 11 cols × 5 rows — a SOLID body with a "monitor" screen cut into it, modelled on
152
+ # the minimal robot icon and kept short/flat to sit beside the three-line banner
153
+ # text. Each cell is ``(palette-role, text)``; get_robot_row() resolves the role
154
+ # to a themed style. Layout: ▄ top cap · screen · eye(▀) + dash(▬) + ear dots(▪)
155
+ # · copper strip(▬) · legs(▀).
156
+ _MASCOT_TEMPLATE = [
157
+ [("", " "), ("shelltop", "▄▄▄▄▄▄▄▄▄"), ("", " ")],
158
+ [("", " "), ("shell", " "), ("screen", " "), ("shell", " "), ("", " ")],
159
+ [
160
+ ("ear", "▪"), ("shell", " "),
161
+ ("screen", " "), ("eye", "▀"), ("screen", " "),
162
+ ("dash", "▬"), ("screen", " "),
163
+ ("shell", " "), ("ear", "▪"),
164
+ ],
165
+ [("", " "), ("strip", "▬▬▬▬▬▬▬▬▬"), ("", " ")],
166
+ [
167
+ ("", " "), ("leg", "▀"), ("", " "), ("leg", "▀"), ("", " "),
168
+ ("leg", "▀"), ("", " "), ("leg", "▀"), ("", " "),
169
+ ],
170
+ ]
171
+
172
+ ROBOT_ROW_COUNT = len(_MASCOT_TEMPLATE)
173
+
174
+
175
+ def _resolve_eyes(state: RobotState, tick: int) -> tuple[str, str]:
176
+ el, er = _EYES[state]
177
+ if state is RobotState.IDLE:
178
+ if tick % 24 in (0, 1):
179
+ el, er = "·", "·"
180
+ elif state is RobotState.THINKING:
181
+ frames = (("◐", "◑"), ("◓", "◒"), ("◑", "◐"), ("◒", "◓"))
182
+ el, er = frames[tick % len(frames)]
183
+ elif state is RobotState.STREAMING:
184
+ el = er = "▸" if (tick % 4) < 2 else "▹"
185
+ return el, er
186
+
187
+
188
+ def get_robot_row(tick: int, row: int) -> list:
189
+ """Return FormattedText fragments for a single robot row, themed.
190
+
191
+ Rows: 0 shell top · 1 screen · 2 ear dots + eyes (square · dash) · 3 copper
192
+ strip · 4 legs. Each role is resolved to a colour for the active light/dark
193
+ theme (see detect_theme()).
194
+ """
195
+ del tick
196
+ pal = _PALETTES[detect_theme()]
197
+ return [(pal[key] if key else "", text) for key, text in _MASCOT_TEMPLATE[row]]
198
+
199
+
200
+ def get_robot_frame(tick: int) -> list:
201
+ """Legacy single-fragment list (not split by row). Use get_robot_row() instead."""
202
+ out = []
203
+ for r in range(ROBOT_ROW_COUNT):
204
+ out += get_robot_row(tick, r)
205
+ return out
206
+
207
+
208
+ def get_status_dot(tick: int) -> list:
209
+ """Compact inline indicator for the status bar — one animated glyph + state label.
210
+
211
+ IDLE: • (copper, slow blink)
212
+ THINKING: ◐ thinking… (yellow spinner)
213
+ STREAMING: ▶ generating… (green pulse)
214
+ ERROR: ✕ error (red)
215
+ DONE: ✓ done (green, brief)
216
+ """
217
+ state = get_robot_state()
218
+ col = _COLOUR[state]
219
+ if detect_theme() == "light":
220
+ col = {
221
+ RobotState.IDLE: "#9A6700",
222
+ RobotState.THINKING: "#9A6700",
223
+ RobotState.STREAMING: "#1A7F37",
224
+ RobotState.ERROR: "#CF222E",
225
+ RobotState.DONE: "#1A7F37",
226
+ }[state]
227
+ el, _ = _resolve_eyes(state, tick)
228
+ if state is RobotState.IDLE:
229
+ el = "•"
230
+ label = _STATUS[state]
231
+
232
+ frags: list = [(f"bold {col}", el)]
233
+ if label:
234
+ frags.append((col, f" {label}"))
235
+ return frags
workspace/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Workspace primitives for Aria Code."""
2
+
3
+ from .files import WorkspaceFiles, WorkspaceSecurity
4
+ from .verify import VerificationPlan, VerificationPlanner
5
+
6
+ __all__ = ["VerificationPlan", "VerificationPlanner", "WorkspaceFiles", "WorkspaceSecurity"]