aury-agent 0.0.4__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 (149) hide show
  1. aury/__init__.py +2 -0
  2. aury/agents/__init__.py +55 -0
  3. aury/agents/a2a/__init__.py +168 -0
  4. aury/agents/backends/__init__.py +196 -0
  5. aury/agents/backends/artifact/__init__.py +9 -0
  6. aury/agents/backends/artifact/memory.py +130 -0
  7. aury/agents/backends/artifact/types.py +133 -0
  8. aury/agents/backends/code/__init__.py +65 -0
  9. aury/agents/backends/file/__init__.py +11 -0
  10. aury/agents/backends/file/local.py +66 -0
  11. aury/agents/backends/file/types.py +40 -0
  12. aury/agents/backends/invocation/__init__.py +8 -0
  13. aury/agents/backends/invocation/memory.py +81 -0
  14. aury/agents/backends/invocation/types.py +110 -0
  15. aury/agents/backends/memory/__init__.py +8 -0
  16. aury/agents/backends/memory/memory.py +179 -0
  17. aury/agents/backends/memory/types.py +136 -0
  18. aury/agents/backends/message/__init__.py +9 -0
  19. aury/agents/backends/message/memory.py +122 -0
  20. aury/agents/backends/message/types.py +124 -0
  21. aury/agents/backends/sandbox.py +275 -0
  22. aury/agents/backends/session/__init__.py +8 -0
  23. aury/agents/backends/session/memory.py +93 -0
  24. aury/agents/backends/session/types.py +124 -0
  25. aury/agents/backends/shell/__init__.py +11 -0
  26. aury/agents/backends/shell/local.py +110 -0
  27. aury/agents/backends/shell/types.py +55 -0
  28. aury/agents/backends/shell.py +209 -0
  29. aury/agents/backends/snapshot/__init__.py +19 -0
  30. aury/agents/backends/snapshot/git.py +95 -0
  31. aury/agents/backends/snapshot/hybrid.py +125 -0
  32. aury/agents/backends/snapshot/memory.py +86 -0
  33. aury/agents/backends/snapshot/types.py +59 -0
  34. aury/agents/backends/state/__init__.py +29 -0
  35. aury/agents/backends/state/composite.py +49 -0
  36. aury/agents/backends/state/file.py +57 -0
  37. aury/agents/backends/state/memory.py +52 -0
  38. aury/agents/backends/state/sqlite.py +262 -0
  39. aury/agents/backends/state/types.py +178 -0
  40. aury/agents/backends/subagent/__init__.py +165 -0
  41. aury/agents/cli/__init__.py +41 -0
  42. aury/agents/cli/chat.py +239 -0
  43. aury/agents/cli/config.py +236 -0
  44. aury/agents/cli/extensions.py +460 -0
  45. aury/agents/cli/main.py +189 -0
  46. aury/agents/cli/session.py +337 -0
  47. aury/agents/cli/workflow.py +276 -0
  48. aury/agents/context_providers/__init__.py +66 -0
  49. aury/agents/context_providers/artifact.py +299 -0
  50. aury/agents/context_providers/base.py +177 -0
  51. aury/agents/context_providers/memory.py +70 -0
  52. aury/agents/context_providers/message.py +130 -0
  53. aury/agents/context_providers/skill.py +50 -0
  54. aury/agents/context_providers/subagent.py +46 -0
  55. aury/agents/context_providers/tool.py +68 -0
  56. aury/agents/core/__init__.py +83 -0
  57. aury/agents/core/base.py +573 -0
  58. aury/agents/core/context.py +797 -0
  59. aury/agents/core/context_builder.py +303 -0
  60. aury/agents/core/event_bus/__init__.py +15 -0
  61. aury/agents/core/event_bus/bus.py +203 -0
  62. aury/agents/core/factory.py +169 -0
  63. aury/agents/core/isolator.py +97 -0
  64. aury/agents/core/logging.py +95 -0
  65. aury/agents/core/parallel.py +194 -0
  66. aury/agents/core/runner.py +139 -0
  67. aury/agents/core/services/__init__.py +5 -0
  68. aury/agents/core/services/file_session.py +144 -0
  69. aury/agents/core/services/message.py +53 -0
  70. aury/agents/core/services/session.py +53 -0
  71. aury/agents/core/signals.py +109 -0
  72. aury/agents/core/state.py +363 -0
  73. aury/agents/core/types/__init__.py +107 -0
  74. aury/agents/core/types/action.py +176 -0
  75. aury/agents/core/types/artifact.py +135 -0
  76. aury/agents/core/types/block.py +736 -0
  77. aury/agents/core/types/message.py +350 -0
  78. aury/agents/core/types/recall.py +144 -0
  79. aury/agents/core/types/session.py +257 -0
  80. aury/agents/core/types/subagent.py +154 -0
  81. aury/agents/core/types/tool.py +205 -0
  82. aury/agents/eval/__init__.py +331 -0
  83. aury/agents/hitl/__init__.py +57 -0
  84. aury/agents/hitl/ask_user.py +242 -0
  85. aury/agents/hitl/compaction.py +230 -0
  86. aury/agents/hitl/exceptions.py +87 -0
  87. aury/agents/hitl/permission.py +617 -0
  88. aury/agents/hitl/revert.py +216 -0
  89. aury/agents/llm/__init__.py +31 -0
  90. aury/agents/llm/adapter.py +367 -0
  91. aury/agents/llm/openai.py +294 -0
  92. aury/agents/llm/provider.py +476 -0
  93. aury/agents/mcp/__init__.py +153 -0
  94. aury/agents/memory/__init__.py +46 -0
  95. aury/agents/memory/compaction.py +394 -0
  96. aury/agents/memory/manager.py +465 -0
  97. aury/agents/memory/processor.py +177 -0
  98. aury/agents/memory/store.py +187 -0
  99. aury/agents/memory/types.py +137 -0
  100. aury/agents/messages/__init__.py +40 -0
  101. aury/agents/messages/config.py +47 -0
  102. aury/agents/messages/raw_store.py +224 -0
  103. aury/agents/messages/store.py +118 -0
  104. aury/agents/messages/types.py +88 -0
  105. aury/agents/middleware/__init__.py +31 -0
  106. aury/agents/middleware/base.py +341 -0
  107. aury/agents/middleware/chain.py +342 -0
  108. aury/agents/middleware/message.py +129 -0
  109. aury/agents/middleware/message_container.py +126 -0
  110. aury/agents/middleware/raw_message.py +153 -0
  111. aury/agents/middleware/truncation.py +139 -0
  112. aury/agents/middleware/types.py +81 -0
  113. aury/agents/plugin.py +162 -0
  114. aury/agents/react/__init__.py +4 -0
  115. aury/agents/react/agent.py +1923 -0
  116. aury/agents/sandbox/__init__.py +23 -0
  117. aury/agents/sandbox/local.py +239 -0
  118. aury/agents/sandbox/remote.py +200 -0
  119. aury/agents/sandbox/types.py +115 -0
  120. aury/agents/skill/__init__.py +16 -0
  121. aury/agents/skill/loader.py +180 -0
  122. aury/agents/skill/types.py +83 -0
  123. aury/agents/tool/__init__.py +39 -0
  124. aury/agents/tool/builtin/__init__.py +23 -0
  125. aury/agents/tool/builtin/ask_user.py +155 -0
  126. aury/agents/tool/builtin/bash.py +107 -0
  127. aury/agents/tool/builtin/delegate.py +726 -0
  128. aury/agents/tool/builtin/edit.py +121 -0
  129. aury/agents/tool/builtin/plan.py +277 -0
  130. aury/agents/tool/builtin/read.py +91 -0
  131. aury/agents/tool/builtin/thinking.py +111 -0
  132. aury/agents/tool/builtin/yield_result.py +130 -0
  133. aury/agents/tool/decorator.py +252 -0
  134. aury/agents/tool/set.py +204 -0
  135. aury/agents/usage/__init__.py +12 -0
  136. aury/agents/usage/tracker.py +236 -0
  137. aury/agents/workflow/__init__.py +85 -0
  138. aury/agents/workflow/adapter.py +268 -0
  139. aury/agents/workflow/dag.py +116 -0
  140. aury/agents/workflow/dsl.py +575 -0
  141. aury/agents/workflow/executor.py +659 -0
  142. aury/agents/workflow/expression.py +136 -0
  143. aury/agents/workflow/parser.py +182 -0
  144. aury/agents/workflow/state.py +145 -0
  145. aury/agents/workflow/types.py +86 -0
  146. aury_agent-0.0.4.dist-info/METADATA +90 -0
  147. aury_agent-0.0.4.dist-info/RECORD +149 -0
  148. aury_agent-0.0.4.dist-info/WHEEL +4 -0
  149. aury_agent-0.0.4.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,239 @@
1
+ """
2
+ 交互式对话命令
3
+ ==============
4
+
5
+ 提供命令行交互式对话功能。
6
+ """
7
+ import asyncio
8
+ from typing import Optional, Any
9
+ from uuid import uuid4
10
+
11
+ from rich.console import Console
12
+ from rich.markdown import Markdown
13
+ from rich.panel import Panel
14
+ from rich.prompt import Prompt
15
+
16
+ from aury.agents.core import Session, InvocationContext
17
+ from aury.agents.react import ReactAgent
18
+
19
+ console = Console()
20
+
21
+
22
+ class DefaultChatAgent(ReactAgent):
23
+ """默认对话 Agent。"""
24
+
25
+ name = "chat_agent"
26
+ description = "通用对话助手"
27
+ system_prompt = """你是一个友好的 AI 助手。请用中文回答用户的问题。"""
28
+ tools = []
29
+
30
+
31
+ async def chat_command(
32
+ agent_name: Optional[str] = None,
33
+ session_id: Optional[str] = None,
34
+ config: Optional[dict] = None,
35
+ verbose: bool = False,
36
+ ):
37
+ """
38
+ 运行交互式对话。
39
+
40
+ Args:
41
+ agent_name: Agent 名称或模块路径
42
+ session_id: 会话 ID(可选,用于恢复会话)
43
+ config: 配置字典
44
+ verbose: 是否显示详细信息
45
+ """
46
+ config = config or {}
47
+
48
+ # 显示欢迎信息
49
+ console.print(Panel(
50
+ "[bold blue]Aury Agent 交互式对话[/bold blue]\n\n"
51
+ "输入消息与 Agent 对话\n"
52
+ "输入 /help 查看可用命令\n"
53
+ "输入 /quit 或按 Ctrl+C 退出",
54
+ title="欢迎",
55
+ ))
56
+
57
+ # 创建或加载会话
58
+ if session_id:
59
+ console.print(f"[dim]恢复会话: {session_id}[/dim]")
60
+ session = Session(session_id=session_id)
61
+ # TODO: 从存储加载会话
62
+ else:
63
+ session_id = f"chat-{uuid4().hex[:8]}"
64
+ session = Session(session_id=session_id)
65
+ console.print(f"[dim]新会话: {session_id}[/dim]")
66
+
67
+ # 创建 Agent
68
+ agent = await _create_agent(agent_name, config)
69
+
70
+ if verbose:
71
+ console.print(f"[dim]使用 Agent: {agent.name}[/dim]")
72
+
73
+ # 创建上下文
74
+ context = InvocationContext(session=session)
75
+
76
+ # 主循环
77
+ while True:
78
+ try:
79
+ # 获取用户输入
80
+ user_input = Prompt.ask("\n[bold cyan]你[/bold cyan]")
81
+
82
+ if not user_input.strip():
83
+ continue
84
+
85
+ # 处理命令
86
+ if user_input.startswith("/"):
87
+ should_continue = await _handle_command(
88
+ user_input, session, context, verbose
89
+ )
90
+ if not should_continue:
91
+ break
92
+ continue
93
+
94
+ # 运行 Agent
95
+ console.print("[dim]思考中...[/dim]")
96
+
97
+ try:
98
+ result = await agent.run(user_input, context)
99
+
100
+ # 显示响应
101
+ console.print()
102
+ console.print(Panel(
103
+ Markdown(result.output) if result.output else "[dim]无响应[/dim]",
104
+ title="[bold green]Agent[/bold green]",
105
+ border_style="green",
106
+ ))
107
+
108
+ # 显示工具调用(详细模式)
109
+ if verbose and hasattr(result, 'tool_calls') and result.tool_calls:
110
+ console.print("[dim]工具调用:[/dim]")
111
+ for tc in result.tool_calls:
112
+ console.print(f" [dim]- {tc.name}[/dim]")
113
+
114
+ except Exception as e:
115
+ console.print(f"[red]错误: {e}[/red]")
116
+ if verbose:
117
+ import traceback
118
+ console.print(f"[dim]{traceback.format_exc()}[/dim]")
119
+
120
+ except KeyboardInterrupt:
121
+ console.print("\n[yellow]收到中断信号[/yellow]")
122
+ break
123
+ except EOFError:
124
+ break
125
+
126
+ # 退出
127
+ console.print("\n[dim]再见![/dim]")
128
+
129
+
130
+ async def _create_agent(
131
+ agent_name: Optional[str],
132
+ config: dict,
133
+ ) -> ReactAgent:
134
+ """创建 Agent 实例。"""
135
+
136
+ if agent_name is None:
137
+ # 使用默认 Agent
138
+ return DefaultChatAgent()
139
+
140
+ # 尝试从模块加载
141
+ if "." in agent_name or agent_name.endswith(".py"):
142
+ try:
143
+ import importlib.util
144
+ from pathlib import Path
145
+
146
+ if agent_name.endswith(".py"):
147
+ # 从文件加载
148
+ path = Path(agent_name)
149
+ spec = importlib.util.spec_from_file_location("agent_module", path)
150
+ module = importlib.util.module_from_spec(spec)
151
+ spec.loader.exec_module(module)
152
+ else:
153
+ # 从模块路径加载
154
+ module = importlib.import_module(agent_name)
155
+
156
+ # 查找 Agent 类
157
+ for name in dir(module):
158
+ obj = getattr(module, name)
159
+ if (
160
+ isinstance(obj, type)
161
+ and issubclass(obj, ReactAgent)
162
+ and obj is not ReactAgent
163
+ ):
164
+ return obj()
165
+
166
+ raise ValueError(f"模块中未找到 Agent 类: {agent_name}")
167
+
168
+ except Exception as e:
169
+ console.print(f"[yellow]无法加载 Agent '{agent_name}': {e}[/yellow]")
170
+ console.print("[dim]使用默认 Agent[/dim]")
171
+ return DefaultChatAgent()
172
+
173
+ # TODO: 从注册表查找已注册的 Agent
174
+ console.print(f"[yellow]未找到 Agent '{agent_name}',使用默认 Agent[/yellow]")
175
+ return DefaultChatAgent()
176
+
177
+
178
+ async def _handle_command(
179
+ command: str,
180
+ session: Session,
181
+ context: InvocationContext,
182
+ verbose: bool,
183
+ ) -> bool:
184
+ """
185
+ 处理斜杠命令。
186
+
187
+ Returns:
188
+ bool: 是否继续对话
189
+ """
190
+ cmd = command.strip().lower()
191
+
192
+ if cmd in ["/quit", "/exit", "/q"]:
193
+ return False
194
+
195
+ elif cmd == "/help":
196
+ console.print(Panel(
197
+ "[bold]可用命令:[/bold]\n\n"
198
+ "/help - 显示帮助信息\n"
199
+ "/quit - 退出对话\n"
200
+ "/clear - 清空对话历史\n"
201
+ "/session - 显示当前会话信息\n"
202
+ "/history - 显示对话历史\n"
203
+ "/save - 保存当前会话\n"
204
+ "/verbose - 切换详细模式",
205
+ title="帮助",
206
+ ))
207
+
208
+ elif cmd == "/clear":
209
+ context.history = []
210
+ console.print("[green]对话历史已清空[/green]")
211
+
212
+ elif cmd == "/session":
213
+ console.print(Panel(
214
+ f"会话 ID: {session.session_id}\n"
215
+ f"消息数: {len(context.history) if hasattr(context, 'history') else 0}",
216
+ title="会话信息",
217
+ ))
218
+
219
+ elif cmd == "/history":
220
+ if hasattr(context, 'history') and context.history:
221
+ for msg in context.history[-10:]: # 最近 10 条
222
+ role = msg.get("role", "unknown")
223
+ content = msg.get("content", "")[:100]
224
+ console.print(f"[dim]{role}: {content}...[/dim]")
225
+ else:
226
+ console.print("[dim]暂无对话历史[/dim]")
227
+
228
+ elif cmd == "/save":
229
+ # TODO: 实现会话保存
230
+ console.print("[green]会话已保存[/green]")
231
+
232
+ elif cmd == "/verbose":
233
+ console.print(f"[dim]详细模式: {'开启' if verbose else '关闭'}[/dim]")
234
+
235
+ else:
236
+ console.print(f"[yellow]未知命令: {command}[/yellow]")
237
+ console.print("[dim]输入 /help 查看可用命令[/dim]")
238
+
239
+ return True
@@ -0,0 +1,236 @@
1
+ """
2
+ 配置管理命令
3
+ ============
4
+
5
+ 管理 Aury Agent 的配置文件。
6
+ """
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Optional, Any
10
+
11
+ import typer
12
+ import yaml
13
+ from rich.console import Console
14
+ from rich.panel import Panel
15
+ from rich.syntax import Syntax
16
+
17
+ config_app = typer.Typer(help="配置管理命令")
18
+ console = Console()
19
+
20
+ # 默认配置目录
21
+ DEFAULT_CONFIG_DIR = Path.home() / ".aury"
22
+ DEFAULT_CONFIG_FILE = DEFAULT_CONFIG_DIR / "config.yaml"
23
+
24
+
25
+ def get_default_config() -> dict:
26
+ """获取默认配置。"""
27
+ return {
28
+ "agent": {
29
+ "default": "chat_agent",
30
+ "max_iterations": 20,
31
+ },
32
+ "model": {
33
+ "provider": "openai",
34
+ "model": "gpt-4",
35
+ "temperature": 0.7,
36
+ "max_tokens": 4096,
37
+ },
38
+ "session": {
39
+ "storage_path": str(DEFAULT_CONFIG_DIR / "sessions"),
40
+ "auto_save": True,
41
+ "max_history": 100,
42
+ },
43
+ "logging": {
44
+ "level": "INFO",
45
+ "file": None,
46
+ },
47
+ }
48
+
49
+
50
+ def load_config(config_path: Optional[Path] = None) -> dict:
51
+ """
52
+ 加载配置文件。
53
+
54
+ Args:
55
+ config_path: 配置文件路径,为 None 时使用默认路径
56
+
57
+ Returns:
58
+ 配置字典
59
+ """
60
+ if config_path is None:
61
+ config_path = DEFAULT_CONFIG_FILE
62
+
63
+ config = get_default_config()
64
+
65
+ if config_path.exists():
66
+ try:
67
+ with open(config_path, "r", encoding="utf-8") as f:
68
+ user_config = yaml.safe_load(f) or {}
69
+
70
+ # 合并用户配置
71
+ _deep_merge(config, user_config)
72
+ except Exception as e:
73
+ console.print(f"[yellow]警告: 无法加载配置文件 {config_path}: {e}[/yellow]")
74
+
75
+ return config
76
+
77
+
78
+ def save_config(config: dict, config_path: Optional[Path] = None) -> None:
79
+ """保存配置文件。"""
80
+ if config_path is None:
81
+ config_path = DEFAULT_CONFIG_FILE
82
+
83
+ # 确保目录存在
84
+ config_path.parent.mkdir(parents=True, exist_ok=True)
85
+
86
+ with open(config_path, "w", encoding="utf-8") as f:
87
+ yaml.safe_dump(config, f, allow_unicode=True, default_flow_style=False)
88
+
89
+
90
+ def _deep_merge(base: dict, override: dict) -> None:
91
+ """深度合并字典。"""
92
+ for key, value in override.items():
93
+ if key in base and isinstance(base[key], dict) and isinstance(value, dict):
94
+ _deep_merge(base[key], value)
95
+ else:
96
+ base[key] = value
97
+
98
+
99
+ @config_app.command("show")
100
+ def show_config(
101
+ config_file: Optional[Path] = typer.Option(
102
+ None,
103
+ "--file", "-f",
104
+ help="配置文件路径",
105
+ ),
106
+ ):
107
+ """显示当前配置。"""
108
+ config = load_config(config_file)
109
+
110
+ yaml_str = yaml.safe_dump(config, allow_unicode=True, default_flow_style=False)
111
+
112
+ console.print(Panel(
113
+ Syntax(yaml_str, "yaml", theme="monokai"),
114
+ title="当前配置",
115
+ ))
116
+
117
+
118
+ @config_app.command("init")
119
+ def init_config(
120
+ force: bool = typer.Option(
121
+ False,
122
+ "--force", "-f",
123
+ help="强制覆盖已存在的配置",
124
+ ),
125
+ ):
126
+ """初始化默认配置文件。"""
127
+ if DEFAULT_CONFIG_FILE.exists() and not force:
128
+ console.print(f"[yellow]配置文件已存在: {DEFAULT_CONFIG_FILE}[/yellow]")
129
+ console.print("[dim]使用 --force 覆盖[/dim]")
130
+ raise typer.Exit(1)
131
+
132
+ # 创建默认配置
133
+ config = get_default_config()
134
+ save_config(config)
135
+
136
+ console.print(f"[green]配置文件已创建: {DEFAULT_CONFIG_FILE}[/green]")
137
+
138
+
139
+ @config_app.command("set")
140
+ def set_config(
141
+ key: str = typer.Argument(..., help="配置项键名(用点号分隔,如 model.temperature)"),
142
+ value: str = typer.Argument(..., help="配置项值"),
143
+ config_file: Optional[Path] = typer.Option(
144
+ None,
145
+ "--file", "-f",
146
+ help="配置文件路径",
147
+ ),
148
+ ):
149
+ """设置配置项。"""
150
+ config = load_config(config_file)
151
+
152
+ # 解析键路径
153
+ keys = key.split(".")
154
+ current = config
155
+
156
+ for k in keys[:-1]:
157
+ if k not in current:
158
+ current[k] = {}
159
+ current = current[k]
160
+
161
+ # 尝试解析值类型
162
+ try:
163
+ # 尝试解析为数字
164
+ if "." in value:
165
+ parsed_value = float(value)
166
+ else:
167
+ parsed_value = int(value)
168
+ except ValueError:
169
+ # 尝试解析为布尔值
170
+ if value.lower() in ["true", "yes"]:
171
+ parsed_value = True
172
+ elif value.lower() in ["false", "no"]:
173
+ parsed_value = False
174
+ elif value.lower() == "null":
175
+ parsed_value = None
176
+ else:
177
+ parsed_value = value
178
+
179
+ current[keys[-1]] = parsed_value
180
+
181
+ # 保存
182
+ save_config(config, config_file)
183
+
184
+ console.print(f"[green]已设置 {key} = {parsed_value}[/green]")
185
+
186
+
187
+ @config_app.command("get")
188
+ def get_config(
189
+ key: str = typer.Argument(..., help="配置项键名"),
190
+ config_file: Optional[Path] = typer.Option(
191
+ None,
192
+ "--file", "-f",
193
+ help="配置文件路径",
194
+ ),
195
+ ):
196
+ """获取配置项值。"""
197
+ config = load_config(config_file)
198
+
199
+ # 解析键路径
200
+ keys = key.split(".")
201
+ current = config
202
+
203
+ try:
204
+ for k in keys:
205
+ current = current[k]
206
+ console.print(f"{key} = {current}")
207
+ except KeyError:
208
+ console.print(f"[red]配置项不存在: {key}[/red]")
209
+ raise typer.Exit(1)
210
+
211
+
212
+ @config_app.command("path")
213
+ def config_path():
214
+ """显示配置文件路径。"""
215
+ console.print(f"配置目录: {DEFAULT_CONFIG_DIR}")
216
+ console.print(f"配置文件: {DEFAULT_CONFIG_FILE}")
217
+ console.print(f"文件存在: {'是' if DEFAULT_CONFIG_FILE.exists() else '否'}")
218
+
219
+
220
+ @config_app.command("edit")
221
+ def edit_config():
222
+ """使用默认编辑器编辑配置文件。"""
223
+ import subprocess
224
+
225
+ if not DEFAULT_CONFIG_FILE.exists():
226
+ console.print("[yellow]配置文件不存在,先创建默认配置[/yellow]")
227
+ config = get_default_config()
228
+ save_config(config)
229
+
230
+ editor = os.environ.get("EDITOR", "vim")
231
+
232
+ try:
233
+ subprocess.run([editor, str(DEFAULT_CONFIG_FILE)])
234
+ except FileNotFoundError:
235
+ console.print(f"[red]编辑器未找到: {editor}[/red]")
236
+ console.print(f"[dim]请设置 EDITOR 环境变量或手动编辑: {DEFAULT_CONFIG_FILE}[/dim]")