mycode-coding-agent 0.1.0__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.
- mycode/__init__.py +0 -0
- mycode/adapters/__init__.py +21 -0
- mycode/adapters/jsonl.py +692 -0
- mycode/agent/__init__.py +25 -0
- mycode/agent/events.py +111 -0
- mycode/agent/outcome.py +103 -0
- mycode/agent/progress.py +373 -0
- mycode/agent/runner.py +1481 -0
- mycode/application/__init__.py +38 -0
- mycode/application/agent_session.py +367 -0
- mycode/application/events.py +59 -0
- mycode/application/runtime.py +211 -0
- mycode/application/sessions.py +180 -0
- mycode/cli.py +840 -0
- mycode/config.py +355 -0
- mycode/context/__init__.py +1 -0
- mycode/context/artifacts.py +672 -0
- mycode/context/budget.py +752 -0
- mycode/context/builder.py +112 -0
- mycode/context/compact.py +795 -0
- mycode/context/tool_result_format.py +199 -0
- mycode/context/tool_result_retention.py +261 -0
- mycode/conversation.py +78 -0
- mycode/error_handling.py +481 -0
- mycode/event_format.py +147 -0
- mycode/instructions.py +285 -0
- mycode/llm.py +771 -0
- mycode/mcp/__init__.py +41 -0
- mycode/mcp/client.py +44 -0
- mycode/mcp/config.py +207 -0
- mycode/mcp/errors.py +302 -0
- mycode/mcp/manager.py +339 -0
- mycode/mcp/models.py +20 -0
- mycode/mcp/result_adapter.py +58 -0
- mycode/mcp/tool_adapter.py +145 -0
- mycode/mcp/trust.py +313 -0
- mycode/memory.py +570 -0
- mycode/memory_context.py +245 -0
- mycode/messages.py +63 -0
- mycode/observability.py +28 -0
- mycode/permissions.py +262 -0
- mycode/persistence/__init__.py +1 -0
- mycode/persistence/filesystem.py +291 -0
- mycode/persistence/project_storage.py +208 -0
- mycode/persistence/session_lock.py +138 -0
- mycode/persistence/session_store.py +503 -0
- mycode/presentation/__init__.py +1 -0
- mycode/presentation/cli/__init__.py +14 -0
- mycode/presentation/cli/confirmer.py +116 -0
- mycode/presentation/cli/mcp_trust.py +61 -0
- mycode/presentation/cli/presenter.py +320 -0
- mycode/presentation/cli/session_menu.py +146 -0
- mycode/presentation/cli/subagent_observer.py +124 -0
- mycode/presentation/command_format.py +90 -0
- mycode/presentation/commands.py +95 -0
- mycode/presentation/tui/__init__.py +6 -0
- mycode/presentation/tui/app.py +1351 -0
- mycode/presentation/tui/interactions.py +253 -0
- mycode/presentation/tui/presenter.py +266 -0
- mycode/presentation/tui/screens.py +305 -0
- mycode/presentation/tui/widgets.py +214 -0
- mycode/project.py +22 -0
- mycode/prompts.py +181 -0
- mycode/reasoning.py +40 -0
- mycode/session.py +86 -0
- mycode/skills/__init__.py +27 -0
- mycode/skills/builtin/database-recovery/SKILL.md +138 -0
- mycode/skills/builtin/database-recovery/references/sqlite.md +235 -0
- mycode/skills/registry.py +295 -0
- mycode/skills/state.py +68 -0
- mycode/subagents/__init__.py +1 -0
- mycode/subagents/audit.py +212 -0
- mycode/subagents/concurrency.py +124 -0
- mycode/subagents/contracts.py +421 -0
- mycode/subagents/delegate.py +80 -0
- mycode/subagents/delegation.py +128 -0
- mycode/subagents/lifecycle.py +86 -0
- mycode/subagents/limits.py +7 -0
- mycode/subagents/observability.py +150 -0
- mycode/subagents/persistence.py +152 -0
- mycode/subagents/profiles.py +184 -0
- mycode/subagents/prompts.py +67 -0
- mycode/subagents/results.py +178 -0
- mycode/subagents/runtime.py +528 -0
- mycode/subagents/snapshots.py +211 -0
- mycode/subagents/tool_batch.py +260 -0
- mycode/tools/__init__.py +81 -0
- mycode/tools/base.py +222 -0
- mycode/tools/bounds.py +14 -0
- mycode/tools/command_executor.py +167 -0
- mycode/tools/command_output.py +166 -0
- mycode/tools/command_risk.py +596 -0
- mycode/tools/defaults.py +59 -0
- mycode/tools/edit_file.py +524 -0
- mycode/tools/file_mutation.py +30 -0
- mycode/tools/glob.py +247 -0
- mycode/tools/grep.py +324 -0
- mycode/tools/ignore.py +122 -0
- mycode/tools/inspect_changes.py +269 -0
- mycode/tools/load_skill.py +92 -0
- mycode/tools/memory.py +264 -0
- mycode/tools/path_permissions.py +78 -0
- mycode/tools/patterns.py +48 -0
- mycode/tools/permission_metadata.py +27 -0
- mycode/tools/process_tree.py +166 -0
- mycode/tools/read_file.py +242 -0
- mycode/tools/read_skill_resource.py +93 -0
- mycode/tools/registry.py +279 -0
- mycode/tools/run_command.py +237 -0
- mycode/tools/run_skill_script.py +206 -0
- mycode/tools/run_validation.py +107 -0
- mycode/tools/submit_result.py +93 -0
- mycode/tools/text.py +15 -0
- mycode/tools/validation_command.py +377 -0
- mycode/tools/workspace.py +33 -0
- mycode/tools/write_file.py +169 -0
- mycode_coding_agent-0.1.0.dist-info/METADATA +244 -0
- mycode_coding_agent-0.1.0.dist-info/RECORD +121 -0
- mycode_coding_agent-0.1.0.dist-info/WHEEL +4 -0
- mycode_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
- mycode_coding_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
mycode/cli.py
ADDED
|
@@ -0,0 +1,840 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
from mycode.agent.events import AgentEvent
|
|
8
|
+
from mycode.adapters.jsonl import run_jsonl_runtime
|
|
9
|
+
from mycode.application.events import RuntimeEvent
|
|
10
|
+
from mycode.application import (
|
|
11
|
+
AgentApplicationSession,
|
|
12
|
+
CompactResult,
|
|
13
|
+
ContextStatus,
|
|
14
|
+
context_budget_from_config,
|
|
15
|
+
list_project_sessions,
|
|
16
|
+
run_agent_turn,
|
|
17
|
+
SessionStartRequest,
|
|
18
|
+
start_agent_application_session,
|
|
19
|
+
)
|
|
20
|
+
from mycode.presentation.cli.confirmer import TerminalConfirmer
|
|
21
|
+
from mycode.presentation.cli.presenter import CliDisplayMode, CliPresenter
|
|
22
|
+
from mycode.config import LLMConfig, load_llm_config
|
|
23
|
+
from mycode.context.compact import ConversationCompactor
|
|
24
|
+
from mycode.context.budget import (
|
|
25
|
+
ContextBudgetExceededError,
|
|
26
|
+
format_model_context_stats,
|
|
27
|
+
)
|
|
28
|
+
from mycode.error_handling import error_summary, format_model_error
|
|
29
|
+
from mycode.conversation import Conversation
|
|
30
|
+
from mycode.mcp import (
|
|
31
|
+
MCPConfig,
|
|
32
|
+
MCPConfigError,
|
|
33
|
+
load_mcp_config_layers,
|
|
34
|
+
resolve_project_mcp_trust,
|
|
35
|
+
)
|
|
36
|
+
from mycode.observability import ObservationSink
|
|
37
|
+
from mycode.llm import OpenAICompatibleLLMClient
|
|
38
|
+
from mycode.project import ProjectIdentity
|
|
39
|
+
from mycode.agent.runner import AgentRunner
|
|
40
|
+
from mycode.agent.outcome import AgentRunOutcome
|
|
41
|
+
from mycode.session import ChatSession
|
|
42
|
+
from mycode.persistence.session_store import (
|
|
43
|
+
SessionInUseError,
|
|
44
|
+
SessionNotFoundError,
|
|
45
|
+
SessionStore,
|
|
46
|
+
SessionStoreError,
|
|
47
|
+
)
|
|
48
|
+
from mycode.presentation.cli.subagent_observer import CliSubAgentObserver
|
|
49
|
+
from mycode.presentation.cli.session_menu import select_session_request
|
|
50
|
+
from mycode.presentation.cli.mcp_trust import TerminalMCPTrustConfirmer
|
|
51
|
+
from mycode.presentation.commands import (
|
|
52
|
+
CommandParseError,
|
|
53
|
+
ParsedCommand,
|
|
54
|
+
parse_slash_command,
|
|
55
|
+
)
|
|
56
|
+
from mycode.presentation.command_format import (
|
|
57
|
+
format_command_help,
|
|
58
|
+
format_compact_result,
|
|
59
|
+
format_context_status,
|
|
60
|
+
format_session_list,
|
|
61
|
+
)
|
|
62
|
+
from mycode.presentation.tui.app import run_tui
|
|
63
|
+
from mycode.tools import (
|
|
64
|
+
Workspace,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
_CHAT_EXIT_COMMANDS = {"/exit", "/quit"}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def build_chat_session(llm_config: LLMConfig | None = None) -> ChatSession:
|
|
72
|
+
config = load_llm_config() if llm_config is None else llm_config
|
|
73
|
+
session_id = uuid4().hex
|
|
74
|
+
client = OpenAICompatibleLLMClient(config=config, session_id=session_id)
|
|
75
|
+
summary_client = OpenAICompatibleLLMClient(
|
|
76
|
+
config=config,
|
|
77
|
+
model=config.compact_model,
|
|
78
|
+
thinking_enabled=False,
|
|
79
|
+
session_id=session_id,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return ChatSession(
|
|
83
|
+
llm_client=client,
|
|
84
|
+
context_budget=context_budget_from_config(config),
|
|
85
|
+
compactor=ConversationCompactor(llm_client=summary_client),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def run_chat_loop(
|
|
90
|
+
session: ChatSession,
|
|
91
|
+
input_func: Callable[[str], str] = input,
|
|
92
|
+
output_func: Callable[[str], None] = print,
|
|
93
|
+
output_chunk_func: Callable[[str], None] | None = None,
|
|
94
|
+
) -> None:
|
|
95
|
+
if output_chunk_func is None:
|
|
96
|
+
output_chunk_func = _print_chunk
|
|
97
|
+
|
|
98
|
+
output_func("输入 /exit 或 /quit 退出。")
|
|
99
|
+
|
|
100
|
+
while True:
|
|
101
|
+
try:
|
|
102
|
+
content = input_func("you> ").strip()
|
|
103
|
+
except EOFError:
|
|
104
|
+
output_func("")
|
|
105
|
+
break
|
|
106
|
+
except KeyboardInterrupt:
|
|
107
|
+
output_func("")
|
|
108
|
+
output_func("提示> Chat 已中断。")
|
|
109
|
+
break
|
|
110
|
+
|
|
111
|
+
if content in _CHAT_EXIT_COMMANDS:
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
if content == "":
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
chunks = iter(session.stream_user_message(content))
|
|
119
|
+
except ContextBudgetExceededError as error:
|
|
120
|
+
output_func(
|
|
121
|
+
"context> "
|
|
122
|
+
+ format_model_context_stats(
|
|
123
|
+
error.context,
|
|
124
|
+
previous_prompt_tokens=(
|
|
125
|
+
session.last_token_usage.prompt_tokens
|
|
126
|
+
if session.last_token_usage is not None
|
|
127
|
+
else None
|
|
128
|
+
),
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
output_func(f"error> {error}")
|
|
132
|
+
continue
|
|
133
|
+
except KeyboardInterrupt:
|
|
134
|
+
output_func("")
|
|
135
|
+
output_func("提示> Chat 已中断。")
|
|
136
|
+
break
|
|
137
|
+
except Exception as error:
|
|
138
|
+
output_func(
|
|
139
|
+
"错误> "
|
|
140
|
+
+ format_model_error(error, operation="模型请求准备失败")
|
|
141
|
+
)
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
context = session.last_model_context
|
|
145
|
+
if context is not None:
|
|
146
|
+
output_func(
|
|
147
|
+
"context> "
|
|
148
|
+
+ format_model_context_stats(
|
|
149
|
+
context,
|
|
150
|
+
previous_prompt_tokens=(
|
|
151
|
+
session.last_token_usage.prompt_tokens
|
|
152
|
+
if session.last_token_usage is not None
|
|
153
|
+
else None
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
output_chunk_func("assistant> ")
|
|
159
|
+
try:
|
|
160
|
+
for chunk in chunks:
|
|
161
|
+
output_chunk_func(chunk)
|
|
162
|
+
except KeyboardInterrupt:
|
|
163
|
+
output_func("")
|
|
164
|
+
output_func("提示> Chat 已中断。")
|
|
165
|
+
break
|
|
166
|
+
except Exception as error:
|
|
167
|
+
output_func("")
|
|
168
|
+
output_func(
|
|
169
|
+
"错误> "
|
|
170
|
+
+ format_model_error(error, operation="模型流式请求失败")
|
|
171
|
+
)
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
output_func("")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def run_agent_loop(
|
|
178
|
+
runner: AgentRunner,
|
|
179
|
+
input_func: Callable[[str], str] = input,
|
|
180
|
+
output_func: Callable[[str], None] = print,
|
|
181
|
+
output_chunk_func: Callable[[str], None] | None = None,
|
|
182
|
+
display_mode: CliDisplayMode = "normal",
|
|
183
|
+
*,
|
|
184
|
+
turn_func: Callable[..., AgentRunOutcome] | None = None,
|
|
185
|
+
command_handler: Callable[[ParsedCommand], bool] | None = None,
|
|
186
|
+
) -> AgentRunOutcome | None:
|
|
187
|
+
if output_chunk_func is None:
|
|
188
|
+
output_chunk_func = _print_chunk
|
|
189
|
+
if turn_func is None:
|
|
190
|
+
def effective_turn_func(
|
|
191
|
+
content: str,
|
|
192
|
+
*,
|
|
193
|
+
event_handler: Callable[[AgentEvent], None],
|
|
194
|
+
) -> AgentRunOutcome:
|
|
195
|
+
return run_agent_turn(
|
|
196
|
+
runner,
|
|
197
|
+
content,
|
|
198
|
+
event_handler=event_handler,
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
effective_turn_func = turn_func
|
|
202
|
+
|
|
203
|
+
presenter = CliPresenter(output=output_func, mode=display_mode)
|
|
204
|
+
|
|
205
|
+
for source in getattr(runner, "instruction_sources", ()):
|
|
206
|
+
output_func(f"instructions> 已加载 {source}")
|
|
207
|
+
for warning in getattr(runner, "instruction_warnings", ()):
|
|
208
|
+
output_func(f"instructions> 警告:{warning}")
|
|
209
|
+
for warning in getattr(runner, "skill_warnings", ()):
|
|
210
|
+
output_func(f"skills> 警告:{warning}")
|
|
211
|
+
|
|
212
|
+
output_func("输入 /exit 或 /quit 退出。")
|
|
213
|
+
|
|
214
|
+
last_outcome: AgentRunOutcome | None = None
|
|
215
|
+
while True:
|
|
216
|
+
try:
|
|
217
|
+
content = input_func("you> ").strip()
|
|
218
|
+
except EOFError:
|
|
219
|
+
output_func("")
|
|
220
|
+
break
|
|
221
|
+
|
|
222
|
+
if content == "":
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
command = parse_slash_command(content)
|
|
227
|
+
except CommandParseError as error:
|
|
228
|
+
output_func(f"command> {error}")
|
|
229
|
+
continue
|
|
230
|
+
if command is not None:
|
|
231
|
+
if command.name == "exit" and command_handler is None:
|
|
232
|
+
break
|
|
233
|
+
if command_handler is None:
|
|
234
|
+
output_func(f"command> /{command.name} is not available yet.")
|
|
235
|
+
continue
|
|
236
|
+
if command_handler(command):
|
|
237
|
+
break
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
last_outcome = _run_agent_turn(
|
|
241
|
+
runner=runner,
|
|
242
|
+
content=content,
|
|
243
|
+
output_func=output_func,
|
|
244
|
+
output_chunk_func=output_chunk_func,
|
|
245
|
+
presenter=presenter,
|
|
246
|
+
turn_func=effective_turn_func,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
return last_outcome
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def run_agent_command(
|
|
253
|
+
workspace_path: Path | None = None,
|
|
254
|
+
input_func: Callable[[str], str] = input,
|
|
255
|
+
output_func: Callable[[str], None] = print,
|
|
256
|
+
output_chunk_func: Callable[[str], None] | None = None,
|
|
257
|
+
display_mode: CliDisplayMode = "normal",
|
|
258
|
+
*,
|
|
259
|
+
session_request: SessionStartRequest | None = None,
|
|
260
|
+
session_store: SessionStore | None = None,
|
|
261
|
+
llm_config: LLMConfig | None = None,
|
|
262
|
+
mcp_config: MCPConfig | None = None,
|
|
263
|
+
observability_sink: ObservationSink | None = None,
|
|
264
|
+
) -> None:
|
|
265
|
+
workspace = Workspace(Path.cwd() if workspace_path is None else workspace_path)
|
|
266
|
+
project = ProjectIdentity.from_workspace(workspace.root)
|
|
267
|
+
config = (
|
|
268
|
+
load_llm_config(workspace_root=workspace.root)
|
|
269
|
+
if llm_config is None
|
|
270
|
+
else llm_config
|
|
271
|
+
)
|
|
272
|
+
store = SessionStore() if session_store is None else session_store
|
|
273
|
+
effective_request = session_request
|
|
274
|
+
try:
|
|
275
|
+
if effective_request is None:
|
|
276
|
+
effective_request = select_session_request(
|
|
277
|
+
store,
|
|
278
|
+
project,
|
|
279
|
+
input_func=input_func,
|
|
280
|
+
output_func=output_func,
|
|
281
|
+
)
|
|
282
|
+
except (SessionNotFoundError, SessionInUseError) as error:
|
|
283
|
+
output_func(f"session> 错误:{error}")
|
|
284
|
+
return
|
|
285
|
+
except SessionStoreError as error:
|
|
286
|
+
output_func(f"session> 严重错误:{error}")
|
|
287
|
+
return
|
|
288
|
+
if effective_request is None:
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
if mcp_config is None:
|
|
293
|
+
loaded_mcp_config = load_mcp_config_layers(
|
|
294
|
+
workspace_root=workspace.root
|
|
295
|
+
)
|
|
296
|
+
trust_confirmer = TerminalMCPTrustConfirmer(
|
|
297
|
+
input_func=input_func,
|
|
298
|
+
output_func=output_func,
|
|
299
|
+
)
|
|
300
|
+
trust_resolution = resolve_project_mcp_trust(
|
|
301
|
+
loaded_mcp_config,
|
|
302
|
+
project,
|
|
303
|
+
confirmer=trust_confirmer,
|
|
304
|
+
)
|
|
305
|
+
effective_mcp_config = trust_resolution.config
|
|
306
|
+
else:
|
|
307
|
+
effective_mcp_config = mcp_config
|
|
308
|
+
except MCPConfigError as error:
|
|
309
|
+
output_func("MCP servers:")
|
|
310
|
+
output_func(f"✗ config {error}")
|
|
311
|
+
effective_mcp_config = MCPConfig()
|
|
312
|
+
|
|
313
|
+
confirmer = TerminalConfirmer(
|
|
314
|
+
input_func=input_func,
|
|
315
|
+
output_func=output_func,
|
|
316
|
+
)
|
|
317
|
+
cli_observer = CliSubAgentObserver(output=output_func, mode=display_mode)
|
|
318
|
+
interactive_session_selection = session_request is None
|
|
319
|
+
while True:
|
|
320
|
+
try:
|
|
321
|
+
application_session = start_agent_application_session(
|
|
322
|
+
store,
|
|
323
|
+
project,
|
|
324
|
+
request=effective_request,
|
|
325
|
+
mcp_config=effective_mcp_config,
|
|
326
|
+
confirmer=confirmer,
|
|
327
|
+
external_observer=cli_observer,
|
|
328
|
+
llm_config=config,
|
|
329
|
+
observability_sink=observability_sink,
|
|
330
|
+
)
|
|
331
|
+
except SessionInUseError as error:
|
|
332
|
+
if not interactive_session_selection:
|
|
333
|
+
output_func(f"session> 错误:{error}")
|
|
334
|
+
return
|
|
335
|
+
output_func(f"session> 当前不可用:{error}")
|
|
336
|
+
try:
|
|
337
|
+
effective_request = select_session_request(
|
|
338
|
+
store,
|
|
339
|
+
project,
|
|
340
|
+
input_func=input_func,
|
|
341
|
+
output_func=output_func,
|
|
342
|
+
)
|
|
343
|
+
except (SessionNotFoundError, SessionInUseError) as menu_error:
|
|
344
|
+
output_func(f"session> 错误:{menu_error}")
|
|
345
|
+
return
|
|
346
|
+
except SessionStoreError as menu_error:
|
|
347
|
+
output_func(f"session> 严重错误:{menu_error}")
|
|
348
|
+
return
|
|
349
|
+
if effective_request is None:
|
|
350
|
+
return
|
|
351
|
+
continue
|
|
352
|
+
except SessionNotFoundError as error:
|
|
353
|
+
output_func(f"session> 错误:{error}")
|
|
354
|
+
return
|
|
355
|
+
except SessionStoreError as error:
|
|
356
|
+
output_func(f"session> 严重错误:{error}")
|
|
357
|
+
return
|
|
358
|
+
except Exception as error:
|
|
359
|
+
output_func("")
|
|
360
|
+
output_func(
|
|
361
|
+
"错误> " + format_model_error(error, operation="Agent 运行失败")
|
|
362
|
+
)
|
|
363
|
+
return
|
|
364
|
+
break
|
|
365
|
+
|
|
366
|
+
_output_session_started(application_session, output_func)
|
|
367
|
+
if application_session.compact_state_recovered:
|
|
368
|
+
output_func(
|
|
369
|
+
"session> 警告:无效的 Compact 状态已重置;"
|
|
370
|
+
"已恢复完整历史并进入 Compact 冷却期"
|
|
371
|
+
)
|
|
372
|
+
_output_mcp_statuses(application_session, output_func)
|
|
373
|
+
|
|
374
|
+
def handle_command(command: ParsedCommand) -> bool:
|
|
375
|
+
nonlocal application_session
|
|
376
|
+
|
|
377
|
+
if command.name == "help":
|
|
378
|
+
_output_command_help(output_func)
|
|
379
|
+
return False
|
|
380
|
+
if command.name == "sessions":
|
|
381
|
+
_output_sessions(
|
|
382
|
+
store,
|
|
383
|
+
project,
|
|
384
|
+
current_session_id=application_session.active_project_session.record.id,
|
|
385
|
+
output_func=output_func,
|
|
386
|
+
)
|
|
387
|
+
return False
|
|
388
|
+
if command.name == "context":
|
|
389
|
+
try:
|
|
390
|
+
status = application_session.get_context_status()
|
|
391
|
+
except Exception as error: # noqa: BLE001 - command boundary
|
|
392
|
+
output_func(
|
|
393
|
+
"context> failed: "
|
|
394
|
+
+ format_model_error(error, operation="Context inspection failed")
|
|
395
|
+
)
|
|
396
|
+
else:
|
|
397
|
+
_output_context_status(status, output_func)
|
|
398
|
+
return False
|
|
399
|
+
if command.name == "compact":
|
|
400
|
+
_output_compact_result(application_session.compact_context(), output_func)
|
|
401
|
+
return False
|
|
402
|
+
if command.name == "exit":
|
|
403
|
+
return True
|
|
404
|
+
if command.name not in {"new", "resume"}:
|
|
405
|
+
output_func(f"command> /{command.name} is not available yet.")
|
|
406
|
+
return False
|
|
407
|
+
|
|
408
|
+
if command.name == "resume":
|
|
409
|
+
target_session_id = command.args[0]
|
|
410
|
+
current_session_id = application_session.active_project_session.record.id
|
|
411
|
+
if target_session_id == current_session_id:
|
|
412
|
+
output_func("session> already using current session")
|
|
413
|
+
return False
|
|
414
|
+
request = SessionStartRequest(
|
|
415
|
+
mode="resume",
|
|
416
|
+
session_id=target_session_id,
|
|
417
|
+
)
|
|
418
|
+
else:
|
|
419
|
+
request = SessionStartRequest(mode="new")
|
|
420
|
+
|
|
421
|
+
try:
|
|
422
|
+
replacement = start_agent_application_session(
|
|
423
|
+
store,
|
|
424
|
+
project,
|
|
425
|
+
request=request,
|
|
426
|
+
mcp_config=effective_mcp_config,
|
|
427
|
+
confirmer=confirmer,
|
|
428
|
+
external_observer=cli_observer,
|
|
429
|
+
llm_config=config,
|
|
430
|
+
observability_sink=observability_sink,
|
|
431
|
+
)
|
|
432
|
+
except SessionInUseError as error:
|
|
433
|
+
output_func(f"session> 当前不可用:{error}")
|
|
434
|
+
return False
|
|
435
|
+
except SessionNotFoundError as error:
|
|
436
|
+
output_func(f"session> 错误:{error}")
|
|
437
|
+
return False
|
|
438
|
+
except SessionStoreError as error:
|
|
439
|
+
output_func(f"session> 严重错误:{error}")
|
|
440
|
+
return False
|
|
441
|
+
except Exception as error:
|
|
442
|
+
output_func(
|
|
443
|
+
"错误> " + format_model_error(error, operation="Session 切换失败")
|
|
444
|
+
)
|
|
445
|
+
return False
|
|
446
|
+
|
|
447
|
+
previous = application_session
|
|
448
|
+
application_session = replacement
|
|
449
|
+
try:
|
|
450
|
+
previous.close()
|
|
451
|
+
except Exception as error:
|
|
452
|
+
output_func(
|
|
453
|
+
"session> 警告:"
|
|
454
|
+
+ format_model_error(error, operation="旧 Session 清理失败")
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
_output_session_started(application_session, output_func)
|
|
458
|
+
_output_mcp_statuses(application_session, output_func)
|
|
459
|
+
return False
|
|
460
|
+
|
|
461
|
+
try:
|
|
462
|
+
run_agent_loop(
|
|
463
|
+
runner=application_session.runner,
|
|
464
|
+
input_func=input_func,
|
|
465
|
+
output_func=output_func,
|
|
466
|
+
output_chunk_func=output_chunk_func,
|
|
467
|
+
display_mode=display_mode,
|
|
468
|
+
turn_func=lambda content, *, event_handler: _run_cli_application_turn(
|
|
469
|
+
application_session,
|
|
470
|
+
content,
|
|
471
|
+
event_handler=event_handler,
|
|
472
|
+
),
|
|
473
|
+
command_handler=handle_command,
|
|
474
|
+
)
|
|
475
|
+
except KeyboardInterrupt:
|
|
476
|
+
try:
|
|
477
|
+
application_session.interrupt()
|
|
478
|
+
except SessionStoreError as lifecycle_error:
|
|
479
|
+
output_func(f"session> 警告:{lifecycle_error}")
|
|
480
|
+
output_func("")
|
|
481
|
+
output_func("提示> Agent 已中断,当前进度已保存。")
|
|
482
|
+
except Exception as error:
|
|
483
|
+
try:
|
|
484
|
+
application_session.interrupt()
|
|
485
|
+
except SessionStoreError as lifecycle_error:
|
|
486
|
+
output_func(f"session> 警告:{lifecycle_error}")
|
|
487
|
+
output_func("")
|
|
488
|
+
output_func(
|
|
489
|
+
"错误> " + format_model_error(error, operation="Agent 运行失败")
|
|
490
|
+
)
|
|
491
|
+
else:
|
|
492
|
+
application_session.close()
|
|
493
|
+
finally:
|
|
494
|
+
try:
|
|
495
|
+
application_session.interrupt()
|
|
496
|
+
except SessionStoreError as lifecycle_error:
|
|
497
|
+
output_func(f"session> 警告:{lifecycle_error}")
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def _output_session_started(
|
|
501
|
+
application_session: AgentApplicationSession,
|
|
502
|
+
output_func: Callable[[str], None],
|
|
503
|
+
) -> None:
|
|
504
|
+
active_session = application_session.active_project_session
|
|
505
|
+
if active_session.created:
|
|
506
|
+
output_func(f"session> 已创建新会话 {active_session.record.id}")
|
|
507
|
+
else:
|
|
508
|
+
output_func(
|
|
509
|
+
f"session> 已恢复 {active_session.record.id}:"
|
|
510
|
+
f"{active_session.record.title}"
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def _output_mcp_statuses(
|
|
515
|
+
application_session: AgentApplicationSession,
|
|
516
|
+
output_func: Callable[[str], None],
|
|
517
|
+
) -> None:
|
|
518
|
+
if not application_session.mcp_statuses:
|
|
519
|
+
return
|
|
520
|
+
output_func("MCP servers:")
|
|
521
|
+
for status in application_session.mcp_statuses:
|
|
522
|
+
if status.status == "connected":
|
|
523
|
+
output_func(f"✓ {status.alias:<12} {status.tool_count} tools")
|
|
524
|
+
else:
|
|
525
|
+
output_func(
|
|
526
|
+
f"✗ {status.alias:<12} "
|
|
527
|
+
f"{status.error_summary or status.error_type or 'unavailable'}"
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _output_command_help(output_func: Callable[[str], None]) -> None:
|
|
532
|
+
for line in format_command_help():
|
|
533
|
+
output_func(f"command> {line}")
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def _output_context_status(
|
|
537
|
+
status: ContextStatus,
|
|
538
|
+
output_func: Callable[[str], None],
|
|
539
|
+
) -> None:
|
|
540
|
+
for line in format_context_status(status):
|
|
541
|
+
output_func(f"command> {line}")
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def _output_compact_result(
|
|
545
|
+
result: CompactResult,
|
|
546
|
+
output_func: Callable[[str], None],
|
|
547
|
+
) -> None:
|
|
548
|
+
output_func(f"context> {format_compact_result(result)}")
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _output_sessions(
|
|
552
|
+
store: SessionStore,
|
|
553
|
+
project: ProjectIdentity,
|
|
554
|
+
*,
|
|
555
|
+
current_session_id: str,
|
|
556
|
+
output_func: Callable[[str], None],
|
|
557
|
+
) -> None:
|
|
558
|
+
try:
|
|
559
|
+
sessions = list_project_sessions(store, project, limit=10)
|
|
560
|
+
except SessionStoreError as error:
|
|
561
|
+
output_func(f"session> 严重错误:{error}")
|
|
562
|
+
return
|
|
563
|
+
|
|
564
|
+
for line in format_session_list(
|
|
565
|
+
sessions,
|
|
566
|
+
current_session_id=current_session_id,
|
|
567
|
+
):
|
|
568
|
+
output_func(f"command> {line}")
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def _run_agent_turn(
|
|
572
|
+
*,
|
|
573
|
+
runner: AgentRunner,
|
|
574
|
+
content: str,
|
|
575
|
+
output_func: Callable[[str], None],
|
|
576
|
+
output_chunk_func: Callable[[str], None],
|
|
577
|
+
presenter: CliPresenter,
|
|
578
|
+
turn_func: Callable[..., AgentRunOutcome],
|
|
579
|
+
) -> AgentRunOutcome:
|
|
580
|
+
assistant_started = False
|
|
581
|
+
|
|
582
|
+
def show_event(event: AgentEvent) -> None:
|
|
583
|
+
nonlocal assistant_started
|
|
584
|
+
if event.type == "model_start":
|
|
585
|
+
return
|
|
586
|
+
|
|
587
|
+
if event.type == "text_delta":
|
|
588
|
+
if not assistant_started and event.content.strip() == "":
|
|
589
|
+
return
|
|
590
|
+
if not assistant_started:
|
|
591
|
+
presenter.flush()
|
|
592
|
+
output_chunk_func("assistant> ")
|
|
593
|
+
assistant_started = True
|
|
594
|
+
output_chunk_func(event.content)
|
|
595
|
+
return
|
|
596
|
+
|
|
597
|
+
if assistant_started:
|
|
598
|
+
output_func("")
|
|
599
|
+
assistant_started = False
|
|
600
|
+
|
|
601
|
+
presenter.show_agent_event(event)
|
|
602
|
+
|
|
603
|
+
outcome = turn_func(content, event_handler=show_event)
|
|
604
|
+
|
|
605
|
+
if assistant_started:
|
|
606
|
+
output_func("")
|
|
607
|
+
presenter.flush()
|
|
608
|
+
return outcome
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def _run_cli_application_turn(
|
|
612
|
+
application_session: AgentApplicationSession,
|
|
613
|
+
content: str,
|
|
614
|
+
*,
|
|
615
|
+
event_handler: Callable[[AgentEvent], None],
|
|
616
|
+
) -> AgentRunOutcome:
|
|
617
|
+
def handle_runtime_event(runtime_event: RuntimeEvent) -> None:
|
|
618
|
+
if runtime_event.type == "agent" and runtime_event.agent_event is not None:
|
|
619
|
+
event_handler(runtime_event.agent_event)
|
|
620
|
+
|
|
621
|
+
return application_session.run_turn(
|
|
622
|
+
content,
|
|
623
|
+
event_handler=handle_runtime_event,
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def _print_chunk(content: str) -> None:
|
|
628
|
+
print(content, end="", flush=True)
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def main(argv: list[str] | None = None) -> None:
|
|
632
|
+
args = sys.argv[1:] if argv is None else argv
|
|
633
|
+
|
|
634
|
+
if args == []:
|
|
635
|
+
print("MyCode 已就绪。")
|
|
636
|
+
return
|
|
637
|
+
|
|
638
|
+
parser = _build_cli_parser()
|
|
639
|
+
options = parser.parse_args(args)
|
|
640
|
+
|
|
641
|
+
if options.command == "chat":
|
|
642
|
+
try:
|
|
643
|
+
run_chat_loop(build_chat_session())
|
|
644
|
+
except KeyboardInterrupt:
|
|
645
|
+
print("")
|
|
646
|
+
print("提示> Chat 已中断。")
|
|
647
|
+
except Exception as error:
|
|
648
|
+
print(f"错误> Chat 启动失败:{error_summary(error)}")
|
|
649
|
+
return
|
|
650
|
+
|
|
651
|
+
if options.command == "runtime":
|
|
652
|
+
runtime_request: SessionStartRequest | None = None
|
|
653
|
+
if options.new:
|
|
654
|
+
runtime_request = SessionStartRequest(mode="new")
|
|
655
|
+
elif options.continue_session:
|
|
656
|
+
runtime_request = SessionStartRequest(mode="continue")
|
|
657
|
+
elif options.resume is not None:
|
|
658
|
+
runtime_request = SessionStartRequest(
|
|
659
|
+
mode="resume",
|
|
660
|
+
session_id=options.resume,
|
|
661
|
+
)
|
|
662
|
+
exit_code = run_jsonl_runtime(session_request=runtime_request)
|
|
663
|
+
if exit_code != 0:
|
|
664
|
+
raise SystemExit(exit_code)
|
|
665
|
+
return
|
|
666
|
+
|
|
667
|
+
if options.command == "tui":
|
|
668
|
+
try:
|
|
669
|
+
run_tui()
|
|
670
|
+
except KeyboardInterrupt:
|
|
671
|
+
print("")
|
|
672
|
+
print("提示> TUI 已中断。")
|
|
673
|
+
except Exception as error:
|
|
674
|
+
print(f"错误> TUI 启动失败:{error_summary(error)}")
|
|
675
|
+
return
|
|
676
|
+
|
|
677
|
+
session_request: SessionStartRequest | None = None
|
|
678
|
+
if options.new:
|
|
679
|
+
session_request = SessionStartRequest(mode="new")
|
|
680
|
+
elif options.continue_session:
|
|
681
|
+
session_request = SessionStartRequest(mode="continue")
|
|
682
|
+
elif options.resume is not None:
|
|
683
|
+
session_request = SessionStartRequest(
|
|
684
|
+
mode="resume",
|
|
685
|
+
session_id=options.resume,
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
display_mode: CliDisplayMode = "normal"
|
|
689
|
+
if options.verbose:
|
|
690
|
+
display_mode = "verbose"
|
|
691
|
+
elif options.debug:
|
|
692
|
+
display_mode = "debug"
|
|
693
|
+
|
|
694
|
+
try:
|
|
695
|
+
run_agent_command(
|
|
696
|
+
session_request=session_request,
|
|
697
|
+
display_mode=display_mode,
|
|
698
|
+
)
|
|
699
|
+
except KeyboardInterrupt:
|
|
700
|
+
print("")
|
|
701
|
+
print("提示> Agent 已中断。")
|
|
702
|
+
except Exception as error:
|
|
703
|
+
print(f"错误> Agent 启动失败:{error_summary(error)}")
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def _build_cli_parser() -> argparse.ArgumentParser:
|
|
707
|
+
parser = argparse.ArgumentParser(
|
|
708
|
+
prog="mycode",
|
|
709
|
+
add_help=False,
|
|
710
|
+
description="可扩展终端 coding agent,以启动命令时的当前目录作为工作区。",
|
|
711
|
+
epilog=(
|
|
712
|
+
"示例:\n"
|
|
713
|
+
" mycode agent\n"
|
|
714
|
+
" mycode tui\n"
|
|
715
|
+
" mycode agent --new --verbose\n"
|
|
716
|
+
" mycode agent --resume SESSION_ID\n"
|
|
717
|
+
"\n"
|
|
718
|
+
"使用 'mycode <子命令> --help' 查看对应帮助,"
|
|
719
|
+
"例如 'mycode agent --help'。"
|
|
720
|
+
),
|
|
721
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
722
|
+
)
|
|
723
|
+
_add_help_argument(parser)
|
|
724
|
+
subparsers = parser.add_subparsers(
|
|
725
|
+
dest="command",
|
|
726
|
+
metavar="COMMAND",
|
|
727
|
+
required=True,
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
chat_parser = subparsers.add_parser(
|
|
731
|
+
"chat",
|
|
732
|
+
add_help=False,
|
|
733
|
+
help="启动不带 coding tools 的普通模型对话",
|
|
734
|
+
description="启动不带文件、命令等 coding tools 的普通模型对话。",
|
|
735
|
+
)
|
|
736
|
+
_add_help_argument(chat_parser)
|
|
737
|
+
|
|
738
|
+
tui_parser = subparsers.add_parser(
|
|
739
|
+
"tui",
|
|
740
|
+
add_help=False,
|
|
741
|
+
help="启动 Textual 终端用户界面",
|
|
742
|
+
description="启动 MyCode Textual TUI,支持 Session、Agent Turn、Permission 和交互式命令。",
|
|
743
|
+
)
|
|
744
|
+
_add_help_argument(tui_parser)
|
|
745
|
+
|
|
746
|
+
agent_parser = subparsers.add_parser(
|
|
747
|
+
"agent",
|
|
748
|
+
add_help=False,
|
|
749
|
+
help="在当前目录启动 coding agent",
|
|
750
|
+
description=(
|
|
751
|
+
"在当前目录启动 coding agent。\n"
|
|
752
|
+
"\n"
|
|
753
|
+
"默认行为:\n"
|
|
754
|
+
" 不指定会话选项时,如果当前项目存在历史会话,"
|
|
755
|
+
"则显示交互式会话菜单;\n"
|
|
756
|
+
" 如果没有历史会话,则自动创建新会话。"
|
|
757
|
+
),
|
|
758
|
+
epilog=(
|
|
759
|
+
"示例:\n"
|
|
760
|
+
" mycode agent\n"
|
|
761
|
+
" mycode agent --new\n"
|
|
762
|
+
" mycode agent --continue\n"
|
|
763
|
+
" mycode agent --resume SESSION_ID\n"
|
|
764
|
+
" mycode agent --continue --debug"
|
|
765
|
+
),
|
|
766
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
767
|
+
)
|
|
768
|
+
_add_help_argument(agent_parser)
|
|
769
|
+
session_options = agent_parser.add_argument_group("会话选项")
|
|
770
|
+
session_group = session_options.add_mutually_exclusive_group()
|
|
771
|
+
session_group.add_argument(
|
|
772
|
+
"--new",
|
|
773
|
+
action="store_true",
|
|
774
|
+
help="跳过菜单,直接创建新会话",
|
|
775
|
+
)
|
|
776
|
+
session_group.add_argument(
|
|
777
|
+
"--continue",
|
|
778
|
+
dest="continue_session",
|
|
779
|
+
action="store_true",
|
|
780
|
+
help="跳过菜单,续接最近使用的会话;没有历史会话时新建",
|
|
781
|
+
)
|
|
782
|
+
session_group.add_argument(
|
|
783
|
+
"--resume",
|
|
784
|
+
metavar="SESSION_ID",
|
|
785
|
+
help="跳过菜单,续接指定的未删除会话",
|
|
786
|
+
)
|
|
787
|
+
display_options = agent_parser.add_argument_group("输出选项")
|
|
788
|
+
display_group = display_options.add_mutually_exclusive_group()
|
|
789
|
+
display_group.add_argument(
|
|
790
|
+
"--verbose",
|
|
791
|
+
action="store_true",
|
|
792
|
+
help="显示更完整的运行信息",
|
|
793
|
+
)
|
|
794
|
+
display_group.add_argument(
|
|
795
|
+
"--debug",
|
|
796
|
+
action="store_true",
|
|
797
|
+
help="显示调试级运行信息",
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
runtime_parser = subparsers.add_parser(
|
|
801
|
+
"runtime",
|
|
802
|
+
add_help=False,
|
|
803
|
+
help="启动机器可消费的 JSONL runtime",
|
|
804
|
+
description="启动 version 1 JSONL runtime;stdout 只输出 JSONL。",
|
|
805
|
+
)
|
|
806
|
+
_add_help_argument(runtime_parser)
|
|
807
|
+
runtime_parser.add_argument(
|
|
808
|
+
"--jsonl",
|
|
809
|
+
action="store_true",
|
|
810
|
+
required=True,
|
|
811
|
+
help="启用 JSONL machine protocol",
|
|
812
|
+
)
|
|
813
|
+
runtime_session_options = runtime_parser.add_argument_group("会话选项")
|
|
814
|
+
runtime_session_group = runtime_session_options.add_mutually_exclusive_group()
|
|
815
|
+
runtime_session_group.add_argument(
|
|
816
|
+
"--new",
|
|
817
|
+
action="store_true",
|
|
818
|
+
help="创建新会话",
|
|
819
|
+
)
|
|
820
|
+
runtime_session_group.add_argument(
|
|
821
|
+
"--continue",
|
|
822
|
+
dest="continue_session",
|
|
823
|
+
action="store_true",
|
|
824
|
+
help="续接最近会话;没有历史会话时新建",
|
|
825
|
+
)
|
|
826
|
+
runtime_session_group.add_argument(
|
|
827
|
+
"--resume",
|
|
828
|
+
metavar="SESSION_ID",
|
|
829
|
+
help="续接指定的未删除会话",
|
|
830
|
+
)
|
|
831
|
+
return parser
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def _add_help_argument(parser: argparse.ArgumentParser) -> None:
|
|
835
|
+
parser.add_argument(
|
|
836
|
+
"-h",
|
|
837
|
+
"--help",
|
|
838
|
+
action="help",
|
|
839
|
+
help="显示此帮助并退出",
|
|
840
|
+
)
|