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.
Files changed (121) hide show
  1. mycode/__init__.py +0 -0
  2. mycode/adapters/__init__.py +21 -0
  3. mycode/adapters/jsonl.py +692 -0
  4. mycode/agent/__init__.py +25 -0
  5. mycode/agent/events.py +111 -0
  6. mycode/agent/outcome.py +103 -0
  7. mycode/agent/progress.py +373 -0
  8. mycode/agent/runner.py +1481 -0
  9. mycode/application/__init__.py +38 -0
  10. mycode/application/agent_session.py +367 -0
  11. mycode/application/events.py +59 -0
  12. mycode/application/runtime.py +211 -0
  13. mycode/application/sessions.py +180 -0
  14. mycode/cli.py +840 -0
  15. mycode/config.py +355 -0
  16. mycode/context/__init__.py +1 -0
  17. mycode/context/artifacts.py +672 -0
  18. mycode/context/budget.py +752 -0
  19. mycode/context/builder.py +112 -0
  20. mycode/context/compact.py +795 -0
  21. mycode/context/tool_result_format.py +199 -0
  22. mycode/context/tool_result_retention.py +261 -0
  23. mycode/conversation.py +78 -0
  24. mycode/error_handling.py +481 -0
  25. mycode/event_format.py +147 -0
  26. mycode/instructions.py +285 -0
  27. mycode/llm.py +771 -0
  28. mycode/mcp/__init__.py +41 -0
  29. mycode/mcp/client.py +44 -0
  30. mycode/mcp/config.py +207 -0
  31. mycode/mcp/errors.py +302 -0
  32. mycode/mcp/manager.py +339 -0
  33. mycode/mcp/models.py +20 -0
  34. mycode/mcp/result_adapter.py +58 -0
  35. mycode/mcp/tool_adapter.py +145 -0
  36. mycode/mcp/trust.py +313 -0
  37. mycode/memory.py +570 -0
  38. mycode/memory_context.py +245 -0
  39. mycode/messages.py +63 -0
  40. mycode/observability.py +28 -0
  41. mycode/permissions.py +262 -0
  42. mycode/persistence/__init__.py +1 -0
  43. mycode/persistence/filesystem.py +291 -0
  44. mycode/persistence/project_storage.py +208 -0
  45. mycode/persistence/session_lock.py +138 -0
  46. mycode/persistence/session_store.py +503 -0
  47. mycode/presentation/__init__.py +1 -0
  48. mycode/presentation/cli/__init__.py +14 -0
  49. mycode/presentation/cli/confirmer.py +116 -0
  50. mycode/presentation/cli/mcp_trust.py +61 -0
  51. mycode/presentation/cli/presenter.py +320 -0
  52. mycode/presentation/cli/session_menu.py +146 -0
  53. mycode/presentation/cli/subagent_observer.py +124 -0
  54. mycode/presentation/command_format.py +90 -0
  55. mycode/presentation/commands.py +95 -0
  56. mycode/presentation/tui/__init__.py +6 -0
  57. mycode/presentation/tui/app.py +1351 -0
  58. mycode/presentation/tui/interactions.py +253 -0
  59. mycode/presentation/tui/presenter.py +266 -0
  60. mycode/presentation/tui/screens.py +305 -0
  61. mycode/presentation/tui/widgets.py +214 -0
  62. mycode/project.py +22 -0
  63. mycode/prompts.py +181 -0
  64. mycode/reasoning.py +40 -0
  65. mycode/session.py +86 -0
  66. mycode/skills/__init__.py +27 -0
  67. mycode/skills/builtin/database-recovery/SKILL.md +138 -0
  68. mycode/skills/builtin/database-recovery/references/sqlite.md +235 -0
  69. mycode/skills/registry.py +295 -0
  70. mycode/skills/state.py +68 -0
  71. mycode/subagents/__init__.py +1 -0
  72. mycode/subagents/audit.py +212 -0
  73. mycode/subagents/concurrency.py +124 -0
  74. mycode/subagents/contracts.py +421 -0
  75. mycode/subagents/delegate.py +80 -0
  76. mycode/subagents/delegation.py +128 -0
  77. mycode/subagents/lifecycle.py +86 -0
  78. mycode/subagents/limits.py +7 -0
  79. mycode/subagents/observability.py +150 -0
  80. mycode/subagents/persistence.py +152 -0
  81. mycode/subagents/profiles.py +184 -0
  82. mycode/subagents/prompts.py +67 -0
  83. mycode/subagents/results.py +178 -0
  84. mycode/subagents/runtime.py +528 -0
  85. mycode/subagents/snapshots.py +211 -0
  86. mycode/subagents/tool_batch.py +260 -0
  87. mycode/tools/__init__.py +81 -0
  88. mycode/tools/base.py +222 -0
  89. mycode/tools/bounds.py +14 -0
  90. mycode/tools/command_executor.py +167 -0
  91. mycode/tools/command_output.py +166 -0
  92. mycode/tools/command_risk.py +596 -0
  93. mycode/tools/defaults.py +59 -0
  94. mycode/tools/edit_file.py +524 -0
  95. mycode/tools/file_mutation.py +30 -0
  96. mycode/tools/glob.py +247 -0
  97. mycode/tools/grep.py +324 -0
  98. mycode/tools/ignore.py +122 -0
  99. mycode/tools/inspect_changes.py +269 -0
  100. mycode/tools/load_skill.py +92 -0
  101. mycode/tools/memory.py +264 -0
  102. mycode/tools/path_permissions.py +78 -0
  103. mycode/tools/patterns.py +48 -0
  104. mycode/tools/permission_metadata.py +27 -0
  105. mycode/tools/process_tree.py +166 -0
  106. mycode/tools/read_file.py +242 -0
  107. mycode/tools/read_skill_resource.py +93 -0
  108. mycode/tools/registry.py +279 -0
  109. mycode/tools/run_command.py +237 -0
  110. mycode/tools/run_skill_script.py +206 -0
  111. mycode/tools/run_validation.py +107 -0
  112. mycode/tools/submit_result.py +93 -0
  113. mycode/tools/text.py +15 -0
  114. mycode/tools/validation_command.py +377 -0
  115. mycode/tools/workspace.py +33 -0
  116. mycode/tools/write_file.py +169 -0
  117. mycode_coding_agent-0.1.0.dist-info/METADATA +244 -0
  118. mycode_coding_agent-0.1.0.dist-info/RECORD +121 -0
  119. mycode_coding_agent-0.1.0.dist-info/WHEEL +4 -0
  120. mycode_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
  121. 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
+ )