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
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""SessionCommandsMixin — session list/load/save/export commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from apps.cli.session_export import build_session_export_payload
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SessionCommandsMixin:
|
|
11
|
+
"""Mixin: session list/load/save/export commands."""
|
|
12
|
+
|
|
13
|
+
def cmd_sessions(self, args: str):
|
|
14
|
+
keyword = args.strip().lower()
|
|
15
|
+
sessions = self.terminal.session_mgr.list_sessions()
|
|
16
|
+
if keyword:
|
|
17
|
+
sessions = [s for s in sessions if keyword in s["title"].lower()]
|
|
18
|
+
if not sessions:
|
|
19
|
+
msg = f"No sessions matching '{keyword}'" if keyword else "No saved sessions"
|
|
20
|
+
console.print(f"[dim]{msg}[/dim]" if HAS_RICH else msg)
|
|
21
|
+
return
|
|
22
|
+
if HAS_RICH:
|
|
23
|
+
console.print()
|
|
24
|
+
header = f" [bold]Sessions[/bold] [dim]({len(sessions)} found)[/dim]" if keyword else " [bold]Sessions[/bold]"
|
|
25
|
+
console.print(header)
|
|
26
|
+
for i, s in enumerate(sessions, 1):
|
|
27
|
+
updated = s["updated"][:16] if s["updated"] else "-"
|
|
28
|
+
console.print(f" [dim]{i}.[/dim] [bold]{s['title']}[/bold] "
|
|
29
|
+
f"[dim]{s['id'][:8]} {s['messages']} msgs {updated}[/dim]")
|
|
30
|
+
console.print()
|
|
31
|
+
console.print(" [dim]Use /load <number> to resume · /sessions <keyword> to search[/dim]")
|
|
32
|
+
else:
|
|
33
|
+
for i, s in enumerate(sessions, 1):
|
|
34
|
+
print(f" {i}. [{s['id'][:8]}] {s['title']} ({s['messages']} msgs)")
|
|
35
|
+
|
|
36
|
+
def cmd_save(self, args: str):
|
|
37
|
+
if not self.terminal.conversation:
|
|
38
|
+
console.print("[dim]Nothing to save[/dim]" if HAS_RICH else "Nothing to save")
|
|
39
|
+
return
|
|
40
|
+
sid = self.terminal.session_id
|
|
41
|
+
title = args.strip().strip('"').strip("'") if args.strip() else None
|
|
42
|
+
meta = {}
|
|
43
|
+
if title:
|
|
44
|
+
meta["title"] = title
|
|
45
|
+
self.terminal.session_mgr.save_session(sid, self.terminal.conversation, metadata=meta)
|
|
46
|
+
self.terminal.config["last_session_id"] = sid
|
|
47
|
+
save_config(self.terminal.config)
|
|
48
|
+
display = f"{title} ({sid[:8]})" if title else f"{sid[:8]}..."
|
|
49
|
+
console.print(f"[green]Session saved: {display}[/green]" if HAS_RICH
|
|
50
|
+
else f"Saved: {display}")
|
|
51
|
+
|
|
52
|
+
def cmd_rename(self, args: str):
|
|
53
|
+
"""Rename current session."""
|
|
54
|
+
title = args.strip().strip('"').strip("'")
|
|
55
|
+
if not title:
|
|
56
|
+
console.print("[dim]Usage: /rename <title>[/dim]" if HAS_RICH else "Usage: /rename <title>")
|
|
57
|
+
return
|
|
58
|
+
sid = self.terminal.session_id
|
|
59
|
+
data = self.terminal.session_mgr.load_session(sid)
|
|
60
|
+
if data:
|
|
61
|
+
meta = data.get("metadata", {})
|
|
62
|
+
meta["title"] = title
|
|
63
|
+
self.terminal.session_mgr.save_session(sid, self.terminal.conversation, metadata=meta)
|
|
64
|
+
else:
|
|
65
|
+
self.terminal.session_mgr.save_session(sid, self.terminal.conversation, metadata={"title": title})
|
|
66
|
+
console.print(f"[green]Renamed: {title}[/green]" if HAS_RICH else f"Renamed: {title}")
|
|
67
|
+
|
|
68
|
+
def cmd_load(self, args: str):
|
|
69
|
+
session_id = args.strip()
|
|
70
|
+
if not session_id:
|
|
71
|
+
sessions = self.terminal.session_mgr.list_sessions()
|
|
72
|
+
if not sessions:
|
|
73
|
+
console.print("[dim]No sessions. Usage: /load <session_id>[/dim]" if HAS_RICH
|
|
74
|
+
else "No sessions")
|
|
75
|
+
return
|
|
76
|
+
options = []
|
|
77
|
+
for s in sessions[:20]:
|
|
78
|
+
title = s.get("metadata", {}).get("title", s["id"][:8])
|
|
79
|
+
ts = s.get("updated", "")[:10]
|
|
80
|
+
options.append((title, ts))
|
|
81
|
+
choice = _arrow_select(options, selected=0, title="Load Session")
|
|
82
|
+
if 0 <= choice < len(sessions):
|
|
83
|
+
session_id = sessions[choice]["id"]
|
|
84
|
+
else:
|
|
85
|
+
if HAS_RICH:
|
|
86
|
+
console.print("[dim]Cancelled[/dim]")
|
|
87
|
+
else:
|
|
88
|
+
print("Cancelled")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
data = self.terminal.session_mgr.load_session(session_id)
|
|
92
|
+
if data:
|
|
93
|
+
self.terminal.conversation = data.get("messages", [])
|
|
94
|
+
self.terminal.session_id = data["id"]
|
|
95
|
+
title = data.get("metadata", {}).get("title", "Untitled")
|
|
96
|
+
n = len(self.terminal.conversation)
|
|
97
|
+
console.print(f"[green]Loaded: {title} ({n} messages)[/green]" if HAS_RICH
|
|
98
|
+
else f"Loaded: {title} ({n} msgs)")
|
|
99
|
+
else:
|
|
100
|
+
_print_error(f"Session not found: {session_id}", "session")
|
|
101
|
+
|
|
102
|
+
def cmd_recall(self, args: str):
|
|
103
|
+
"""Full-text search across all saved sessions: /recall <query>"""
|
|
104
|
+
query = args.strip()
|
|
105
|
+
if not query:
|
|
106
|
+
console.print("[dim]Usage: /recall <query>[/dim]" if HAS_RICH else "Usage: /recall <query>")
|
|
107
|
+
return
|
|
108
|
+
results = self.terminal.session_mgr.search_sessions(query)
|
|
109
|
+
if not results:
|
|
110
|
+
msg = f"No sessions found matching '{query}'"
|
|
111
|
+
console.print(f"[dim]{msg}[/dim]" if HAS_RICH else msg)
|
|
112
|
+
return
|
|
113
|
+
if HAS_RICH:
|
|
114
|
+
console.print()
|
|
115
|
+
console.print(f" [bold]Recall[/bold] [dim]{len(results)} session(s) match '{query}'[/dim]")
|
|
116
|
+
console.print()
|
|
117
|
+
for r in results[:10]:
|
|
118
|
+
updated = r["updated"][:16] if r["updated"] else ""
|
|
119
|
+
console.print(
|
|
120
|
+
f" [bold]{r['title']}[/bold] "
|
|
121
|
+
f"[dim]{r['id'][:8]} {r['match_count']} hit(s) {updated}[/dim]"
|
|
122
|
+
)
|
|
123
|
+
preview = r["preview"].replace("\n", " ")[:100]
|
|
124
|
+
console.print(f" [dim]…{preview}…[/dim]")
|
|
125
|
+
console.print()
|
|
126
|
+
console.print(" [dim]Use /load <id> to resume a session[/dim]")
|
|
127
|
+
else:
|
|
128
|
+
print(f"\n{len(results)} session(s) found:")
|
|
129
|
+
for r in results[:10]:
|
|
130
|
+
print(f" [{r['id'][:8]}] {r['title']} ({r['match_count']} hits)")
|
|
131
|
+
print(f" ...{r['preview'][:80]}...")
|
|
132
|
+
|
|
133
|
+
async def cmd_export(self, args: str):
|
|
134
|
+
parts = args.split()
|
|
135
|
+
fmt = parts[0].lower() if parts else "json"
|
|
136
|
+
filename = parts[1] if len(parts) > 1 else None
|
|
137
|
+
|
|
138
|
+
if not self.terminal.conversation:
|
|
139
|
+
console.print("[dim]Nothing to export[/dim]" if HAS_RICH else "Nothing to export")
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
provider_health = []
|
|
144
|
+
if fmt == "bundle":
|
|
145
|
+
try:
|
|
146
|
+
from packages.aria_services.provider_health import GLOBAL_PROVIDER_HEALTH
|
|
147
|
+
provider_health = GLOBAL_PROVIDER_HEALTH.snapshot()
|
|
148
|
+
except Exception:
|
|
149
|
+
provider_health = []
|
|
150
|
+
content, ext, prefix = build_session_export_payload(
|
|
151
|
+
fmt,
|
|
152
|
+
self.terminal.conversation,
|
|
153
|
+
session_id=self.terminal.session_id,
|
|
154
|
+
config=self.terminal.config,
|
|
155
|
+
trace=getattr(self.terminal, "runtime_trace", None),
|
|
156
|
+
provider_health=provider_health,
|
|
157
|
+
)
|
|
158
|
+
except ValueError as exc:
|
|
159
|
+
if fmt == "sft" and "No user→assistant pairs" in str(exc):
|
|
160
|
+
console.print("[dim]No user→assistant pairs to export[/dim]" if HAS_RICH else "No pairs to export")
|
|
161
|
+
return
|
|
162
|
+
console.print("[dim]Format: json, csv, md, sft, or bundle[/dim]" if HAS_RICH
|
|
163
|
+
else "Format: json, csv, md, sft, bundle")
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
if fmt == "sft":
|
|
167
|
+
pairs = json.loads(content)
|
|
168
|
+
if HAS_RICH:
|
|
169
|
+
console.print(f"[dim]{len(pairs)} training pairs extracted[/dim]")
|
|
170
|
+
else:
|
|
171
|
+
print(f"{len(pairs)} training pairs")
|
|
172
|
+
|
|
173
|
+
if not filename:
|
|
174
|
+
from datetime import datetime
|
|
175
|
+
filename = f"{prefix}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.{ext}"
|
|
176
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
177
|
+
f.write(content)
|
|
178
|
+
console.print(f"[green]Exported to {filename}[/green]" if HAS_RICH
|
|
179
|
+
else f"Exported: {filename}")
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""SessionUxCommandsMixin — clear, recap, history, compact, fork, copy commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SessionUxCommandsMixin:
|
|
9
|
+
"""Mixin: session UX and conversation management commands."""
|
|
10
|
+
|
|
11
|
+
def cmd_clear(self, args: str):
|
|
12
|
+
self.terminal.conversation = []
|
|
13
|
+
os.system("clear" if os.name == "posix" else "cls")
|
|
14
|
+
console.print("[dim]Conversation cleared[/dim]" if HAS_RICH else "Cleared")
|
|
15
|
+
|
|
16
|
+
def cmd_btw(self, args: str):
|
|
17
|
+
q = args.strip()
|
|
18
|
+
if not q:
|
|
19
|
+
console.print("[dim]/btw <question> — quick question without polluting history[/dim]" if HAS_RICH else "/btw <question>")
|
|
20
|
+
return
|
|
21
|
+
conv = self.terminal.conversation
|
|
22
|
+
if not conv:
|
|
23
|
+
console.print("[dim](no conversation context yet)[/dim]" if HAS_RICH else "(no context)")
|
|
24
|
+
return
|
|
25
|
+
_ctx_slice = conv[-6:] if len(conv) >= 6 else conv
|
|
26
|
+
_ctx = "\n".join(
|
|
27
|
+
f"{m['role'].upper()}: {str(m.get('content', ''))[:300]}"
|
|
28
|
+
for m in _ctx_slice
|
|
29
|
+
)
|
|
30
|
+
_btw_prompt = (
|
|
31
|
+
f"[Side question — answer briefly, do not reference this note]\n"
|
|
32
|
+
f"Context from conversation:\n{_ctx}\n\nQuestion: {q}"
|
|
33
|
+
)
|
|
34
|
+
if HAS_RICH:
|
|
35
|
+
from rich.panel import Panel as _Panel
|
|
36
|
+
from rich import box as _rbox
|
|
37
|
+
console.print(_Panel(f"[dim]{q}[/dim]", title="[dim]/btw[/dim]", box=_rbox.ROUNDED, border_style="dim"))
|
|
38
|
+
import asyncio as _aio
|
|
39
|
+
|
|
40
|
+
async def _ask_btw():
|
|
41
|
+
try:
|
|
42
|
+
_result = await stream_provider_result(
|
|
43
|
+
OllamaProvider(
|
|
44
|
+
self.terminal.config.get("ollama_url", "http://localhost:11434"),
|
|
45
|
+
self.terminal.config.get("model", "qwen2.5:7b"),
|
|
46
|
+
show_market_prefetch_status=False,
|
|
47
|
+
),
|
|
48
|
+
_btw_prompt,
|
|
49
|
+
tools=[],
|
|
50
|
+
)
|
|
51
|
+
if _result.get("success"):
|
|
52
|
+
return _result.get("response", "")
|
|
53
|
+
return f"(error: {_result.get('error', '')})"
|
|
54
|
+
except Exception as _e:
|
|
55
|
+
return f"(error: {_e})"
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
loop = _aio.get_event_loop()
|
|
59
|
+
answer = loop.run_until_complete(_ask_btw()) if not loop.is_running() else "(run /btw from interactive prompt)"
|
|
60
|
+
except Exception:
|
|
61
|
+
answer = "(could not get answer)"
|
|
62
|
+
if HAS_RICH:
|
|
63
|
+
from rich.panel import Panel as _Panel
|
|
64
|
+
from rich import box as _rbox
|
|
65
|
+
console.print(_Panel(answer.strip(), title="[dim]↩ btw[/dim]", box=_rbox.ROUNDED, border_style="dim #C08050"))
|
|
66
|
+
else:
|
|
67
|
+
print(f"\n [btw] {answer.strip()}\n")
|
|
68
|
+
|
|
69
|
+
def cmd_recap(self, args: str):
|
|
70
|
+
conv = self.terminal.conversation
|
|
71
|
+
if not conv:
|
|
72
|
+
console.print("[dim]No conversation yet[/dim]" if HAS_RICH else "No conversation")
|
|
73
|
+
return
|
|
74
|
+
turns = len([m for m in conv if m.get("role") == "user"])
|
|
75
|
+
topics: list[str] = []
|
|
76
|
+
for m in conv:
|
|
77
|
+
if m.get("role") == "user":
|
|
78
|
+
snippet = str(m.get("content", ""))[:60].strip()
|
|
79
|
+
if snippet:
|
|
80
|
+
topics.append(snippet)
|
|
81
|
+
if HAS_RICH:
|
|
82
|
+
from rich.panel import Panel as _Panel
|
|
83
|
+
from rich import box as _rbox
|
|
84
|
+
body = f"[dim]{turns} 轮对话[/dim]\n"
|
|
85
|
+
for i, t in enumerate(topics[-6:], 1):
|
|
86
|
+
body += f" [dim]{i}.[/dim] {t}…\n"
|
|
87
|
+
console.print(_Panel(body.rstrip(), title="[bold]会话摘要[/bold]", box=_rbox.ROUNDED, border_style="dim"))
|
|
88
|
+
else:
|
|
89
|
+
print(f"Session: {turns} turns")
|
|
90
|
+
for i, t in enumerate(topics[-6:], 1):
|
|
91
|
+
print(f" {i}. {t}…")
|
|
92
|
+
|
|
93
|
+
def cmd_history(self, args: str):
|
|
94
|
+
if not self.terminal.conversation:
|
|
95
|
+
console.print("[dim]No conversation history[/dim]" if HAS_RICH else "No history")
|
|
96
|
+
return
|
|
97
|
+
for msg in self.terminal.conversation[-10:]:
|
|
98
|
+
role = msg["role"]
|
|
99
|
+
content = msg["content"][:120]
|
|
100
|
+
if HAS_RICH:
|
|
101
|
+
prefix = "You" if role == "user" else "Aria"
|
|
102
|
+
style = "bold" if role == "user" else "bold"
|
|
103
|
+
console.print(f"[{style}]{prefix}:[/{style}] [dim]{content}[/dim]")
|
|
104
|
+
else:
|
|
105
|
+
print(f"{'You' if role == 'user' else 'Aria'}: {content}")
|
|
106
|
+
|
|
107
|
+
def cmd_compact(self, args: str):
|
|
108
|
+
if "--hard" in args:
|
|
109
|
+
if len(self.terminal.conversation) > 10:
|
|
110
|
+
kept = self.terminal.conversation[-6:]
|
|
111
|
+
self.terminal.conversation = kept
|
|
112
|
+
console.print(f"[dim]Hard-compacted to last {len(kept)} messages[/dim]" if HAS_RICH
|
|
113
|
+
else f"Hard-compacted to {len(kept)} messages")
|
|
114
|
+
else:
|
|
115
|
+
console.print("[dim]Context small enough, no compaction needed[/dim]" if HAS_RICH
|
|
116
|
+
else "No compaction needed")
|
|
117
|
+
return
|
|
118
|
+
import asyncio as _asyncio
|
|
119
|
+
try:
|
|
120
|
+
loop = _asyncio.get_event_loop()
|
|
121
|
+
loop.run_until_complete(self._smart_compact_async(silent=False))
|
|
122
|
+
except RuntimeError:
|
|
123
|
+
if len(self.terminal.conversation) > 6:
|
|
124
|
+
from apps.cli.message_processing import compact_messages
|
|
125
|
+
compacted = compact_messages(
|
|
126
|
+
self.terminal.conversation,
|
|
127
|
+
model_key=self.terminal.config.get("model", "qwen2.5:7b"),
|
|
128
|
+
)
|
|
129
|
+
self.terminal.conversation = (
|
|
130
|
+
compacted
|
|
131
|
+
if len(compacted) < len(self.terminal.conversation)
|
|
132
|
+
else self.terminal.conversation[-8:]
|
|
133
|
+
)
|
|
134
|
+
console.print("[dim]Compacted (fallback)[/dim]")
|
|
135
|
+
|
|
136
|
+
async def _smart_compact_async(self, silent: bool = False):
|
|
137
|
+
conv = self.terminal.conversation
|
|
138
|
+
if len(conv) <= 4:
|
|
139
|
+
if not silent:
|
|
140
|
+
console.print("[dim]Context small enough — no compaction needed[/dim]" if HAS_RICH
|
|
141
|
+
else "Context small enough")
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
if not silent and HAS_RICH:
|
|
145
|
+
console.print("[dim]Summarising conversation...[/dim]")
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
max_ctx = int(get_model_cfg(self.terminal.config.get("model", "qwen2.5:7b")).get("num_ctx", 16384) or 16384)
|
|
149
|
+
except Exception:
|
|
150
|
+
max_ctx = 16384
|
|
151
|
+
from packages.aria_services.context import build_context_service
|
|
152
|
+
context_service = build_context_service(max_tokens=max_ctx)
|
|
153
|
+
summary_prompt = context_service.build_summary_prompt(conv)
|
|
154
|
+
|
|
155
|
+
summary = ""
|
|
156
|
+
try:
|
|
157
|
+
result = await stream_provider_result(
|
|
158
|
+
OllamaProvider(
|
|
159
|
+
self.terminal.config.get("ollama_url", "http://localhost:11434"),
|
|
160
|
+
self.terminal.config.get("model", "qwen2.5:7b"),
|
|
161
|
+
show_market_prefetch_status=False,
|
|
162
|
+
),
|
|
163
|
+
summary_prompt,
|
|
164
|
+
[],
|
|
165
|
+
tools=[],
|
|
166
|
+
)
|
|
167
|
+
if result.get("success") and result.get("response"):
|
|
168
|
+
summary = result["response"].strip()
|
|
169
|
+
except Exception:
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
if not summary:
|
|
173
|
+
try:
|
|
174
|
+
compacted = context_service.compact_messages(conv)
|
|
175
|
+
except Exception:
|
|
176
|
+
compacted = []
|
|
177
|
+
self.terminal.conversation = compacted if compacted and len(compacted) < len(conv) else conv[-8:]
|
|
178
|
+
if not silent:
|
|
179
|
+
console.print("[dim]Compacted (summary failed, used local fallback)[/dim]" if HAS_RICH
|
|
180
|
+
else "Compacted (summary fallback)")
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
envelope = context_service.build_summary_envelope(conv, summary)
|
|
184
|
+
self.terminal.conversation = envelope.messages
|
|
185
|
+
new_count = len(self.terminal.conversation)
|
|
186
|
+
old_count = len(conv)
|
|
187
|
+
if not silent:
|
|
188
|
+
if HAS_RICH:
|
|
189
|
+
console.print(
|
|
190
|
+
f" [dim]✓ Compacted {old_count} → {new_count} messages "
|
|
191
|
+
f"(summary preserved context)[/dim]"
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
print(f"Compacted {old_count} → {new_count} messages")
|
|
195
|
+
|
|
196
|
+
def cmd_fork(self, args: str):
|
|
197
|
+
import time as _t
|
|
198
|
+
name = args.strip() or f"fork-{_t.strftime('%H%M%S')}"
|
|
199
|
+
snapshot = {
|
|
200
|
+
"name": name,
|
|
201
|
+
"ts": _t.strftime("%Y-%m-%d %H:%M:%S"),
|
|
202
|
+
"conv": [dict(m) for m in self.terminal.conversation],
|
|
203
|
+
"config": dict(self.terminal.config),
|
|
204
|
+
}
|
|
205
|
+
self.terminal._forks.append(snapshot)
|
|
206
|
+
idx = len(self.terminal._forks) - 1
|
|
207
|
+
if HAS_RICH:
|
|
208
|
+
console.print(
|
|
209
|
+
f" [dim]↳ Forked as [bold]{name}[/bold] "
|
|
210
|
+
f"(fork #{idx}, {len(snapshot['conv'])} messages). "
|
|
211
|
+
f"Restore with /load-fork {idx}[/dim]"
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
print(f"Forked as '{name}' (#{idx}). Restore with /load-fork {idx}")
|
|
215
|
+
|
|
216
|
+
def cmd_load_fork(self, args: str):
|
|
217
|
+
forks = self.terminal._forks
|
|
218
|
+
if not forks:
|
|
219
|
+
console.print("[dim]No forks yet — use /fork to create one[/dim]" if HAS_RICH else "No forks")
|
|
220
|
+
return
|
|
221
|
+
try:
|
|
222
|
+
idx = int(args.strip())
|
|
223
|
+
except (ValueError, IndexError):
|
|
224
|
+
if HAS_RICH:
|
|
225
|
+
for i, f in enumerate(forks):
|
|
226
|
+
console.print(f" [dim]#{i}[/dim] {f['name']} [dim]{f['ts']} {len(f['conv'])} msgs[/dim]")
|
|
227
|
+
else:
|
|
228
|
+
for i, f in enumerate(forks):
|
|
229
|
+
print(f" #{i} {f['name']} {f['ts']}")
|
|
230
|
+
return
|
|
231
|
+
if idx < 0 or idx >= len(forks):
|
|
232
|
+
console.print(f"[dim]Fork #{idx} not found[/dim]" if HAS_RICH else "Invalid index")
|
|
233
|
+
return
|
|
234
|
+
snap = forks[idx]
|
|
235
|
+
self.terminal.conversation = [dict(m) for m in snap["conv"]]
|
|
236
|
+
console.print(
|
|
237
|
+
f" [dim]✓ Restored fork [bold]{snap['name']}[/bold] "
|
|
238
|
+
f"({len(snap['conv'])} messages)[/dim]"
|
|
239
|
+
if HAS_RICH else f"Restored fork '{snap['name']}'"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def cmd_copy(self, args: str):
|
|
243
|
+
text = self.terminal._last_response
|
|
244
|
+
if not text:
|
|
245
|
+
console.print("[dim]No response to copy yet[/dim]" if HAS_RICH else "Nothing to copy")
|
|
246
|
+
return
|
|
247
|
+
copied = False
|
|
248
|
+
try:
|
|
249
|
+
import subprocess as _sp
|
|
250
|
+
_sp.run(["pbcopy"], input=text.encode(), check=True, timeout=3)
|
|
251
|
+
copied = True
|
|
252
|
+
except Exception:
|
|
253
|
+
pass
|
|
254
|
+
if not copied:
|
|
255
|
+
try:
|
|
256
|
+
import subprocess as _sp
|
|
257
|
+
_sp.run(["xclip", "-selection", "clipboard"], input=text.encode(), check=True, timeout=3)
|
|
258
|
+
copied = True
|
|
259
|
+
except Exception:
|
|
260
|
+
pass
|
|
261
|
+
if not copied:
|
|
262
|
+
try:
|
|
263
|
+
import subprocess as _sp
|
|
264
|
+
_sp.run(["xdotool", "type", "--clearmodifiers", text], check=True, timeout=3)
|
|
265
|
+
copied = True
|
|
266
|
+
except Exception:
|
|
267
|
+
pass
|
|
268
|
+
if copied:
|
|
269
|
+
self.terminal._record_feedback("copy", text)
|
|
270
|
+
preview = text[:60].replace("\n", " ")
|
|
271
|
+
console.print(
|
|
272
|
+
f" [dim]✓ Copied to clipboard: \"{preview}{'…' if len(text) > 60 else ''}\"[/dim]"
|
|
273
|
+
if HAS_RICH else f"Copied: \"{preview}\""
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
console.print(
|
|
277
|
+
"[yellow]Could not reach clipboard (pbcopy/xclip not found). "
|
|
278
|
+
"Here is the response:[/yellow]\n" + text
|
|
279
|
+
if HAS_RICH else "Clipboard unavailable. Response:\n" + text
|
|
280
|
+
)
|