mindcode 0.5.3__tar.gz → 0.5.5__tar.gz

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 (101) hide show
  1. {mindcode-0.5.3 → mindcode-0.5.5}/PKG-INFO +6 -6
  2. {mindcode-0.5.3 → mindcode-0.5.5}/README.md +5 -5
  3. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/_version.py +1 -1
  4. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/acp_agent/__main__.py +20 -0
  5. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/acp_agent/agent.py +24 -8
  6. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/approval.py +4 -4
  7. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/commands/shell.py +4 -0
  8. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/commands/task.py +4 -4
  9. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/shell/repl.py +46 -5
  10. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/shell/startup.py +0 -8
  11. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/shell/tui.py +150 -30
  12. mindcode-0.5.5/mindcode/config/system.md +25 -0
  13. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/config.py +8 -3
  14. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/policy.py +7 -7
  15. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/render.py +17 -17
  16. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/runtime.py +103 -22
  17. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode.egg-info/PKG-INFO +6 -6
  18. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode.egg-info/SOURCES.txt +1 -0
  19. {mindcode-0.5.3 → mindcode-0.5.5}/pyproject.toml +2 -2
  20. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_acp_agent.py +83 -0
  21. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_approval.py +9 -9
  22. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_bench.py +3 -3
  23. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_diff_capture.py +2 -2
  24. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_exec_command_guard.py +1 -1
  25. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_external_file_approval.py +4 -4
  26. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_render_exec.py +3 -3
  27. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_shell_session.py +1 -1
  28. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_shell_tui.py +155 -24
  29. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_subagents.py +1 -1
  30. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_system_prompt.py +32 -0
  31. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/__init__.py +0 -0
  32. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/__main__.py +0 -0
  33. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/acp_agent/__init__.py +0 -0
  34. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/__init__.py +0 -0
  35. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/adapters/__init__.py +0 -0
  36. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/adapters/base.py +0 -0
  37. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/adapters/mini_bfcl.py +0 -0
  38. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/adapters/mini_gaia.py +0 -0
  39. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/adapters/mini_terminal.py +0 -0
  40. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/adapters/swe_bench.py +0 -0
  41. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/adapters/terminal_bench.py +0 -0
  42. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/fake_tools.py +0 -0
  43. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/jsonutil.py +0 -0
  44. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/paths.py +0 -0
  45. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/predictions.py +0 -0
  46. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/report.py +0 -0
  47. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/runner.py +0 -0
  48. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/schema.py +0 -0
  49. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/scorers/base.py +0 -0
  50. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/scorers/composite.py +0 -0
  51. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/scorers/exact.py +0 -0
  52. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/scorers/json_call.py +0 -0
  53. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/scorers/swe.py +0 -0
  54. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/scorers/terminal.py +0 -0
  55. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/scorers/terminal_bench.py +0 -0
  56. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/trace.py +0 -0
  57. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/bench/workspace.py +0 -0
  58. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/__init__.py +0 -0
  59. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/_shared.py +0 -0
  60. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/commands/__init__.py +0 -0
  61. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/commands/bench.py +0 -0
  62. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/commands/chat.py +0 -0
  63. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/commands/config_cmd.py +0 -0
  64. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/commands/remote.py +0 -0
  65. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/commands/status.py +0 -0
  66. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/commands/terminal.py +0 -0
  67. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/shell/__init__.py +0 -0
  68. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/shell/completion.py +0 -0
  69. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/shell/input_pipeline.py +0 -0
  70. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/shell/menu.py +0 -0
  71. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/cli/shell/slash.py +0 -0
  72. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/config/settings.toml +0 -0
  73. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/mcp.py +0 -0
  74. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/remote.py +0 -0
  75. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/skills.py +0 -0
  76. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/subagents.py +0 -0
  77. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/tasking.py +0 -0
  78. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/terminal/__init__.py +0 -0
  79. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/terminal/__main__.py +0 -0
  80. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode/terminal_core.py +0 -0
  81. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode.egg-info/dependency_links.txt +0 -0
  82. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode.egg-info/entry_points.txt +0 -0
  83. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode.egg-info/requires.txt +0 -0
  84. {mindcode-0.5.3 → mindcode-0.5.5}/mindcode.egg-info/top_level.txt +0 -0
  85. {mindcode-0.5.3 → mindcode-0.5.5}/setup.cfg +0 -0
  86. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_cli_commands.py +0 -0
  87. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_config.py +0 -0
  88. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_policy_choice.py +0 -0
  89. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_policy_timeout.py +0 -0
  90. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_provider_retry_defaults.py +0 -0
  91. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_reasoner_compat.py +0 -0
  92. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_remote.py +0 -0
  93. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_shell_channels.py +0 -0
  94. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_shell_completion.py +0 -0
  95. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_shell_interrupt.py +0 -0
  96. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_shell_slash_priority.py +0 -0
  97. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_skills_mcp.py +0 -0
  98. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_slash_resume.py +0 -0
  99. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_task_resume.py +0 -0
  100. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_tasking.py +0 -0
  101. {mindcode-0.5.3 → mindcode-0.5.5}/tests/test_terminal.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mindcode
3
- Version: 0.5.3
3
+ Version: 0.5.5
4
4
  Summary: codex-style interactive coding agent on top of mindagent
5
5
  Author: mindcode
6
6
  License: MIT
@@ -35,7 +35,7 @@ Requires-Dist: prompt-toolkit>=3.0.30
35
35
  - **可选 Subagent 模式**:`mindcode --subagents` 启用受限的 master/coder 协作;master 仅能委派,coder 才能访问 workspace
36
36
  - 全屏 TUI:顶部 logo/会话信息、可滚动 Markdown 消息区、固定带边框多行输入框与状态栏
37
37
  - 顶部信息栏显示本会话累计 token 消耗;provider 未返回 usage 时显示 `tokens —`
38
- - Rich 富渲染:Markdown、彩色工具面板、file_edit diff 高亮和流式 LLM 输出
38
+ - Rich 富渲染:Markdown、彩色工具面板、edit diff 高亮和流式 LLM 输出
39
39
  - TUI 内选择菜单:`/models`、`/permissions` 用方向键选择
40
40
  - REPL 权限模式:`ask_user`(默认,写入和危险操作逐项询问)/ `full_accept`(接受权限上限内的操作风险)
41
41
  - 会话持久化与恢复:基于 `LocalConversationStore`,存到 `~/.cache/mindcode/sessions/`
@@ -168,7 +168,7 @@ mindcode --subagents "审查当前改动并运行相关测试"
168
168
  ```bash
169
169
  mindcode # 默认进 shell
170
170
  › 在 workspace 创建 hello.txt 写入 hi
171
- › file_edit (create) hello.txt [WRITE]
171
+ › edit (create) hello.txt [WRITE]
172
172
  ask_user 将在执行前询问;也可切换到 /permissions full_accept
173
173
  ┌─ diff: hello.txt ──────────────────────┐
174
174
  │ +hi │
@@ -333,15 +333,15 @@ mindcode/
333
333
  持久化;运行结束会清理未消费的授权,不自动新增磁盘审计文件。
334
334
  - 普通问答使用 `USER_INPUT_REQUIRED` 与 `submit_user_input()`;旧 `approve_run()`
335
335
  因未绑定授权请求而被拒绝。
336
- - `exec_command` 只限制 `cwd`,不会隔离子进程访问的文件。`sed`、`find`、`git diff`
336
+ - `command` 只限制 `cwd`,不会隔离子进程访问的文件。`sed`、`find`、`git diff`
337
337
  等命令保守按写风险审批,具体范围见
338
338
  [策略与权限](../../docs/guides/policies-and-approval.md)。
339
339
 
340
340
  ## 局限
341
341
 
342
- - 不支持 `/undo`:`exec_command` 的副作用可能不可逆,回滚复杂。
342
+ - 不支持 `/undo`:`command` 的副作用可能不可逆,回滚复杂。
343
343
  - 会话恢复只续聊 user/assistant 历史,不恢复 TaskState / Evidence / pending continuation。
344
344
  - `task resume` 是重新观察 workspace 后创建新 Run,不会重放未确认的旧 Action。
345
345
  - 后台任务目前依赖本机 PID;不提供跨主机监督或精确进程身份恢复。
346
- - `bench run` 第一版仅支持串行(concurrency=1);`exec_command` 无沙箱,
346
+ - `bench run` 第一版仅支持串行(concurrency=1);`command` 无沙箱,
347
347
  SWE-bench / Terminal-Bench 评测依赖本机对应的评测环境。
@@ -19,7 +19,7 @@
19
19
  - **可选 Subagent 模式**:`mindcode --subagents` 启用受限的 master/coder 协作;master 仅能委派,coder 才能访问 workspace
20
20
  - 全屏 TUI:顶部 logo/会话信息、可滚动 Markdown 消息区、固定带边框多行输入框与状态栏
21
21
  - 顶部信息栏显示本会话累计 token 消耗;provider 未返回 usage 时显示 `tokens —`
22
- - Rich 富渲染:Markdown、彩色工具面板、file_edit diff 高亮和流式 LLM 输出
22
+ - Rich 富渲染:Markdown、彩色工具面板、edit diff 高亮和流式 LLM 输出
23
23
  - TUI 内选择菜单:`/models`、`/permissions` 用方向键选择
24
24
  - REPL 权限模式:`ask_user`(默认,写入和危险操作逐项询问)/ `full_accept`(接受权限上限内的操作风险)
25
25
  - 会话持久化与恢复:基于 `LocalConversationStore`,存到 `~/.cache/mindcode/sessions/`
@@ -152,7 +152,7 @@ mindcode --subagents "审查当前改动并运行相关测试"
152
152
  ```bash
153
153
  mindcode # 默认进 shell
154
154
  › 在 workspace 创建 hello.txt 写入 hi
155
- › file_edit (create) hello.txt [WRITE]
155
+ › edit (create) hello.txt [WRITE]
156
156
  ask_user 将在执行前询问;也可切换到 /permissions full_accept
157
157
  ┌─ diff: hello.txt ──────────────────────┐
158
158
  │ +hi │
@@ -317,15 +317,15 @@ mindcode/
317
317
  持久化;运行结束会清理未消费的授权,不自动新增磁盘审计文件。
318
318
  - 普通问答使用 `USER_INPUT_REQUIRED` 与 `submit_user_input()`;旧 `approve_run()`
319
319
  因未绑定授权请求而被拒绝。
320
- - `exec_command` 只限制 `cwd`,不会隔离子进程访问的文件。`sed`、`find`、`git diff`
320
+ - `command` 只限制 `cwd`,不会隔离子进程访问的文件。`sed`、`find`、`git diff`
321
321
  等命令保守按写风险审批,具体范围见
322
322
  [策略与权限](../../docs/guides/policies-and-approval.md)。
323
323
 
324
324
  ## 局限
325
325
 
326
- - 不支持 `/undo`:`exec_command` 的副作用可能不可逆,回滚复杂。
326
+ - 不支持 `/undo`:`command` 的副作用可能不可逆,回滚复杂。
327
327
  - 会话恢复只续聊 user/assistant 历史,不恢复 TaskState / Evidence / pending continuation。
328
328
  - `task resume` 是重新观察 workspace 后创建新 Run,不会重放未确认的旧 Action。
329
329
  - 后台任务目前依赖本机 PID;不提供跨主机监督或精确进程身份恢复。
330
- - `bench run` 第一版仅支持串行(concurrency=1);`exec_command` 无沙箱,
330
+ - `bench run` 第一版仅支持串行(concurrency=1);`command` 无沙箱,
331
331
  SWE-bench / Terminal-Bench 评测依赖本机对应的评测环境。
@@ -1,6 +1,6 @@
1
1
  """mindcode 版本与品牌信息。"""
2
2
 
3
- __version__ = "0.5.3"
3
+ __version__ = "0.5.5"
4
4
 
5
5
  __logo__ = r"""
6
6
  [bold cyan] _ _[/bold cyan]
@@ -14,6 +14,7 @@ from pathlib import Path
14
14
 
15
15
  from acp import run_agent
16
16
 
17
+ from ..policy import ApprovalMode
17
18
  from .agent import _AgentHarness
18
19
 
19
20
 
@@ -37,6 +38,23 @@ def main(argv: list[str] | None = None) -> int:
37
38
  default=None,
38
39
  help="Default workspace used when a client connects without a cwd",
39
40
  )
41
+ parser.add_argument(
42
+ "--approval-mode",
43
+ choices=[mode.value for mode in ApprovalMode],
44
+ default=None,
45
+ help=(
46
+ "Override the approval mode read from config.toml. This is a "
47
+ "host-side launch decision; ACP requests can never change it."
48
+ ),
49
+ )
50
+ parser.add_argument(
51
+ "--allow-dangerous",
52
+ action="store_true",
53
+ help=(
54
+ "Allow ActionRisk.DANGEROUS tools in created sessions. Only for "
55
+ "trusted, attended hosts: it lifts the SessionPolicy ceiling."
56
+ ),
57
+ )
40
58
  parser.add_argument("--verbose", action="store_true", help="stderr debug logs")
41
59
  args = parser.parse_args(argv)
42
60
 
@@ -49,6 +67,8 @@ def main(argv: list[str] | None = None) -> int:
49
67
  harness = _AgentHarness(
50
68
  conn, # type: ignore[arg-type]
51
69
  cwd=Path(args.cwd).expanduser() if args.cwd else None,
70
+ approval_mode=args.approval_mode,
71
+ allow_dangerous=args.allow_dangerous,
52
72
  )
53
73
  harness_holder.append(harness)
54
74
  return harness
@@ -11,8 +11,10 @@ approval callback / event stream. Hard rules honoured here:
11
11
 
12
12
  - stdout carries protocol frames only; every log line goes to stderr.
13
13
  - one run per session at a time; a second ``session/prompt`` is rejected.
14
- - provider/model/approval configuration always comes from ``ConfigStore``
15
- (fresh ``read()`` per session); clients cannot widen the SessionPolicy.
14
+ - provider/model/approval configuration comes from ``ConfigStore`` (fresh
15
+ ``read()`` per session); an ACP *request* can never widen it. Only the host
16
+ that launched this process may override approval/session limits, through
17
+ explicit CLI flags (``--approval-mode``, ``--allow-dangerous``).
16
18
  - unimplemented stable methods (``session/load``, ``session/set_mode``)
17
19
  answer with a JSON-RPC error instead of pretending support.
18
20
  """
@@ -70,9 +72,9 @@ def _stop_reason(result: Any) -> str:
70
72
 
71
73
 
72
74
  def _tool_kind(action_name: str) -> str:
73
- if action_name.startswith("file_read"):
75
+ if action_name.startswith("read"):
74
76
  return "read"
75
- if action_name in ("file_write", "file_edit"):
77
+ if action_name in ("write", "edit"):
76
78
  return "edit"
77
79
  if action_name.startswith("exec_") or "command" in action_name:
78
80
  return "execute"
@@ -121,12 +123,19 @@ class _AgentHarness:
121
123
  config_store: ConfigStore | None = None,
122
124
  cwd: Path | None = None,
123
125
  subagents: bool = False,
126
+ approval_mode: str | None = None,
127
+ allow_dangerous: bool = False,
124
128
  permission_timeout_s: float | None = None,
125
129
  ) -> None:
126
130
  self._conn = conn
127
131
  self._config_store = config_store or ConfigStore()
128
132
  self._cwd = cwd or Path.cwd()
129
133
  self._subagents = subagents
134
+ # Explicit launch-time policy, always from the *host* that started
135
+ # this process (CLI flags, i.e. the user's own settings) — never from
136
+ # an ACP request. ``None`` keeps the ConfigStore value.
137
+ self._approval_mode = approval_mode
138
+ self._allow_dangerous = allow_dangerous
130
139
  self._permission_timeout_from_config = permission_timeout_s is None
131
140
  if permission_timeout_s is None:
132
141
  try:
@@ -186,10 +195,12 @@ class _AgentHarness:
186
195
  store = LocalConversationStore(
187
196
  self._config_store.root / "sessions"
188
197
  )
198
+ effective_mode = self._approval_mode or resolved.approval_mode
199
+ effective_approval = ApprovalMode(effective_mode)
189
200
  runtime = build_runtime(
190
201
  workspace,
191
202
  provider_config=resolved,
192
- approve_mode=ApprovalMode(resolved.approval_mode),
203
+ approve_mode=effective_approval,
193
204
  stream=True,
194
205
  event_handler=None,
195
206
  approval_callback=self._make_approval(resolved),
@@ -203,7 +214,7 @@ class _AgentHarness:
203
214
  ),
204
215
  policy=SessionPolicy(
205
216
  allow_write=True,
206
- allow_dangerous=False,
217
+ allow_dangerous=self._allow_dangerous,
207
218
  max_parallel_actions=1,
208
219
  ),
209
220
  conversation_store=store,
@@ -216,12 +227,13 @@ class _AgentHarness:
216
227
  session_id, session, runtime
217
228
  )
218
229
  logger.info(
219
- "session %s created (workspace=%s provider=%s model=%s approval=%s)",
230
+ "session %s created (workspace=%s provider=%s model=%s approval=%s allow_dangerous=%s)",
220
231
  session_id,
221
232
  workspace,
222
233
  resolved.name,
223
234
  resolved.model,
224
- resolved.approval_mode,
235
+ effective_mode,
236
+ self._allow_dangerous,
225
237
  )
226
238
  return NewSessionResponse(session_id=session_id)
227
239
 
@@ -518,6 +530,8 @@ def build_harness(
518
530
  config_store: ConfigStore | None = None,
519
531
  cwd: Path | None = None,
520
532
  subagents: bool = False,
533
+ approval_mode: str | None = None,
534
+ allow_dangerous: bool = False,
521
535
  ) -> _AgentHarness:
522
536
  """Factory used by ``__main__`` and tests."""
523
537
  return _AgentHarness(
@@ -525,4 +539,6 @@ def build_harness(
525
539
  config_store=config_store,
526
540
  cwd=cwd,
527
541
  subagents=subagents,
542
+ approval_mode=approval_mode,
543
+ allow_dangerous=allow_dangerous,
528
544
  )
@@ -42,17 +42,17 @@ def describe_action(action: ActionRequest) -> str:
42
42
  arguments = (
43
43
  action.arguments if isinstance(action.arguments, dict) else {}
44
44
  )
45
- if name in ("file_edit", "file_write"):
46
- verb = "Edit" if name == "file_edit" else "Write"
45
+ if name in ("edit", "write"):
46
+ verb = "Edit" if name == "edit" else "Write"
47
47
  path = _one_line(arguments.get("path", "?"))
48
48
  return f"{verb} {path} [{risk_label(action.risk)}]"
49
- if name == "exec_command":
49
+ if name == "command":
50
50
  command = arguments.get("command", "")
51
51
  preview = _one_line(command) if isinstance(command, str) else ""
52
52
  if len(preview) > 120:
53
53
  preview = preview[:117] + "..."
54
54
  return f"Command {preview} [{risk_label(action.risk)}]"
55
- if name in ("file_read", "memory_read"):
55
+ if name in ("read", "memory_read"):
56
56
  path = _one_line(arguments.get("path", "?"))
57
57
  return f"Read {path} [{risk_label(action.risk)}]"
58
58
  payload = json.dumps(arguments, ensure_ascii=False, sort_keys=True)
@@ -47,6 +47,9 @@ def shell(
47
47
  no_stream: bool = typer.Option(
48
48
  False, "--no-stream", help="关闭流式输出"
49
49
  ),
50
+ no_markdown: bool = typer.Option(
51
+ False, "--no-markdown", help="关闭交互消息的 Markdown 渲染"
52
+ ),
50
53
  subagents: bool = typer.Option(
51
54
  False,
52
55
  "--subagents",
@@ -118,6 +121,7 @@ def shell(
118
121
  session_id=session,
119
122
  resume=resume,
120
123
  stream=not no_stream,
124
+ markdown_enabled=False if no_markdown else None,
121
125
  subagents=subagents,
122
126
  one_shot_prompt=prompt,
123
127
  max_steps=(
@@ -284,10 +284,10 @@ async def _run_task(
284
284
  policy = SessionPolicy(
285
285
  allowed_tools=frozenset(
286
286
  {
287
- "file_read",
288
- "file_write",
289
- "file_edit",
290
- "exec_command",
287
+ "read",
288
+ "write",
289
+ "edit",
290
+ "command",
291
291
  "memory_read",
292
292
  }
293
293
  ),
@@ -204,10 +204,12 @@ class Shell:
204
204
  max_steps: int = BUILTIN_SETTINGS.run.max_steps,
205
205
  step_timeout_s: float | None = BUILTIN_SETTINGS.run.step_timeout_s,
206
206
  total_timeout_s: float | None = BUILTIN_SETTINGS.run.total_timeout_s,
207
+ markdown_enabled: bool | None = None,
207
208
  ):
208
209
  self.workspace = workspace
209
210
  self.approve_mode = approve_mode
210
211
  self.stream = stream
212
+ self.markdown_enabled = markdown_enabled
211
213
  self.resume_id = resume
212
214
  self.explicit_session_id = session_id
213
215
 
@@ -359,7 +361,9 @@ class Shell:
359
361
  on_submit=self._submit_from_tui,
360
362
  on_exit=self._request_exit,
361
363
  on_interrupt=self._interrupt_current,
364
+ on_push_pending=self._push_pending_now,
362
365
  status_provider=self._status_text,
366
+ markdown_enabled=self.markdown_enabled,
363
367
  )
364
368
  self.renderer.attach_transcript(self._tui.transcript)
365
369
  self.state.choose_option = self._tui.choose
@@ -716,6 +720,42 @@ class Shell:
716
720
  except Exception as exc:
717
721
  self._note(f"未能停止:{exc}")
718
722
 
723
+ async def _push_pending_now(self) -> None:
724
+ """Ctrl-Enter: interrupt the active run and dispatch the oldest pending input."""
725
+ session = getattr(self.state, "session", None)
726
+ if not self._busy or session is None:
727
+ return
728
+ pipe = self._pipeline()
729
+ first = pipe.peek_text()
730
+ if first is None:
731
+ return
732
+ # Move the staged item ahead of the queue; the normal process loop will
733
+ # dispatch it after the current run is cancelled.
734
+ queued: list[str] = []
735
+ while True:
736
+ try:
737
+ item = self._queue.get_nowait()
738
+ except asyncio.QueueEmpty:
739
+ break
740
+ if isinstance(item, str):
741
+ queued.append(item)
742
+ else:
743
+ self._queue.put_nowait(item)
744
+ break
745
+ pipe.discard()
746
+ self._queue.put_nowait(first)
747
+ for item in queued:
748
+ self._queue.put_nowait(item)
749
+ self._sync_pending_display()
750
+ active_id = getattr(session, "active_run_id", None) or getattr(
751
+ self.state, "last_run_id", None
752
+ )
753
+ if active_id:
754
+ try:
755
+ await session.cancel_run(active_id)
756
+ except Exception as exc:
757
+ self._note(f"未能立即推送:{exc}")
758
+
719
759
  async def _process_loop(self) -> None:
720
760
  """轮间调度:先排空 slash 优先队,再跑一条普通消息。
721
761
 
@@ -845,10 +885,6 @@ class Shell:
845
885
  def _print_banner(self) -> None:
846
886
  auth = self.config.auth.get(self.state.active_provider)
847
887
  if self._tui is not None:
848
- self._tui.transcript.add_system(
849
- "已就绪。输入消息开始对话;/help 查看命令;/exit 退出;"
850
- "运行中 Ctrl-C 取回消息继续编辑,Ctrl-D 退出。"
851
- )
852
888
  if self.state.history:
853
889
  self._tui.transcript.add_system(
854
890
  f"已恢复 {len(self.state.history)} 条历史消息。"
@@ -1199,10 +1235,14 @@ def run_shell(
1199
1235
  max_steps: int = BUILTIN_SETTINGS.run.max_steps,
1200
1236
  step_timeout_s: float | None = BUILTIN_SETTINGS.run.step_timeout_s,
1201
1237
  total_timeout_s: float | None = BUILTIN_SETTINGS.run.total_timeout_s,
1238
+ markdown_enabled: bool | None = None,
1202
1239
  settings_defaults: frozenset[str] = frozenset(),
1203
1240
  ) -> int:
1204
1241
  """Synchronous entry: build Shell and run it (or one-shot)."""
1205
- configured = ConfigStore().read().runtime_settings
1242
+ config_snapshot = ConfigStore().read()
1243
+ configured = config_snapshot.runtime_settings
1244
+ if markdown_enabled is None:
1245
+ markdown_enabled = config_snapshot.settings.markdown_enabled
1206
1246
  profile = (
1207
1247
  configured.one_shot
1208
1248
  if one_shot_prompt is not None or subagents
@@ -1226,6 +1266,7 @@ def run_shell(
1226
1266
  max_steps=max_steps,
1227
1267
  step_timeout_s=step_timeout_s,
1228
1268
  total_timeout_s=total_timeout_s,
1269
+ markdown_enabled=markdown_enabled,
1229
1270
  )
1230
1271
 
1231
1272
  if one_shot_prompt:
@@ -41,14 +41,6 @@ def build_welcome_banner(
41
41
  "配置后重新启动"
42
42
  )
43
43
  )
44
- lines.append(Text(""))
45
- lines.append(
46
- Text.from_markup(
47
- "[dim]输入消息开始对话;[cyan]/help[/cyan] 查看命令;"
48
- "[cyan]/exit[/cyan] 退出;运行中 Ctrl-C 取回消息继续编辑,"
49
- "Ctrl-D 退出[/dim]"
50
- )
51
- )
52
44
  return Panel(
53
45
  Group(*lines),
54
46
  border_style="cyan",