mindcode 0.5.2__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.
- {mindcode-0.5.2 → mindcode-0.5.5}/PKG-INFO +6 -6
- {mindcode-0.5.2 → mindcode-0.5.5}/README.md +5 -5
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/_version.py +1 -1
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/acp_agent/__main__.py +20 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/acp_agent/agent.py +89 -14
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/approval.py +4 -4
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/schema.py +5 -3
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/__init__.py +10 -3
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/commands/bench.py +15 -3
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/commands/chat.py +36 -6
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/commands/shell.py +42 -6
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/commands/task.py +30 -12
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/shell/input_pipeline.py +13 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/shell/repl.py +169 -44
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/shell/slash.py +6 -24
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/shell/startup.py +0 -8
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/shell/tui.py +255 -83
- mindcode-0.5.5/mindcode/config/settings.toml +41 -0
- mindcode-0.5.5/mindcode/config/system.md +25 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/config.py +180 -4
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/policy.py +7 -7
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/render.py +17 -17
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/runtime.py +146 -38
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/tasking.py +8 -5
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode.egg-info/PKG-INFO +6 -6
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode.egg-info/SOURCES.txt +2 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/pyproject.toml +4 -1
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_acp_agent.py +139 -3
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_approval.py +9 -9
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_bench.py +3 -15
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_cli_commands.py +95 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_config.py +43 -1
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_diff_capture.py +2 -2
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_exec_command_guard.py +1 -1
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_external_file_approval.py +4 -4
- mindcode-0.5.5/tests/test_provider_retry_defaults.py +185 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_render_exec.py +3 -3
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_shell_channels.py +61 -55
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_shell_interrupt.py +72 -19
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_shell_session.py +1 -1
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_shell_tui.py +155 -24
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_subagents.py +1 -1
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_system_prompt.py +32 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_tasking.py +2 -0
- mindcode-0.5.2/tests/test_provider_retry_defaults.py +0 -80
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/__init__.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/__main__.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/acp_agent/__init__.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/__init__.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/adapters/__init__.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/adapters/base.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/adapters/mini_bfcl.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/adapters/mini_gaia.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/adapters/mini_terminal.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/adapters/swe_bench.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/adapters/terminal_bench.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/fake_tools.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/jsonutil.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/paths.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/predictions.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/report.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/runner.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/scorers/base.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/scorers/composite.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/scorers/exact.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/scorers/json_call.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/scorers/swe.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/scorers/terminal.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/scorers/terminal_bench.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/trace.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/bench/workspace.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/_shared.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/commands/__init__.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/commands/config_cmd.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/commands/remote.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/commands/status.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/commands/terminal.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/shell/__init__.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/shell/completion.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/cli/shell/menu.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/mcp.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/remote.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/skills.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/subagents.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/terminal/__init__.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/terminal/__main__.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode/terminal_core.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode.egg-info/dependency_links.txt +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode.egg-info/entry_points.txt +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode.egg-info/requires.txt +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/mindcode.egg-info/top_level.txt +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/setup.cfg +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_policy_choice.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_policy_timeout.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_reasoner_compat.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_remote.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_shell_completion.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_shell_slash_priority.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_skills_mcp.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_slash_resume.py +0 -0
- {mindcode-0.5.2 → mindcode-0.5.5}/tests/test_task_resume.py +0 -0
- {mindcode-0.5.2 → 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
|
+
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、彩色工具面板、
|
|
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
|
-
›
|
|
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
|
-
- `
|
|
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`:`
|
|
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);`
|
|
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、彩色工具面板、
|
|
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
|
-
›
|
|
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
|
-
- `
|
|
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`:`
|
|
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);`
|
|
330
|
+
- `bench run` 第一版仅支持串行(concurrency=1);`command` 无沙箱,
|
|
331
331
|
SWE-bench / Terminal-Bench 评测依赖本机对应的评测环境。
|
|
@@ -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
|
|
15
|
-
|
|
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
|
"""
|
|
@@ -34,7 +36,6 @@ from acp.schema import (
|
|
|
34
36
|
PermissionOption,
|
|
35
37
|
PromptCapabilities,
|
|
36
38
|
PromptResponse,
|
|
37
|
-
RequestPermissionResponse,
|
|
38
39
|
TextContentBlock,
|
|
39
40
|
ToolCallStart,
|
|
40
41
|
)
|
|
@@ -43,6 +44,8 @@ from mindagent.core import (
|
|
|
43
44
|
AgentSession,
|
|
44
45
|
AgentRuntime,
|
|
45
46
|
EventType,
|
|
47
|
+
ExternalFileReadDecision,
|
|
48
|
+
ExternalFileReadRequest,
|
|
46
49
|
LocalConversationStore,
|
|
47
50
|
RunOutcome,
|
|
48
51
|
SessionEnvironment,
|
|
@@ -50,7 +53,7 @@ from mindagent.core import (
|
|
|
50
53
|
SessionPolicy,
|
|
51
54
|
)
|
|
52
55
|
from ..approval import describe_action
|
|
53
|
-
from ..config import ConfigError, ConfigStore, ProviderConfig
|
|
56
|
+
from ..config import BUILTIN_SETTINGS, ConfigError, ConfigStore, ProviderConfig
|
|
54
57
|
from ..policy import ApprovalMode
|
|
55
58
|
from ..runtime import build_runtime
|
|
56
59
|
|
|
@@ -58,9 +61,6 @@ logger = logging.getLogger("mindcode.acp_agent")
|
|
|
58
61
|
|
|
59
62
|
PROTOCOL_VERSION = 1 # stable ACP v1; see docs/acp-layering.md §5
|
|
60
63
|
|
|
61
|
-
_PERMISSION_TIMEOUT_S = 120.0
|
|
62
|
-
|
|
63
|
-
|
|
64
64
|
def _stop_reason(result: Any) -> str:
|
|
65
65
|
"""Map a native run result to an ACP v1 stop reason (layering §3)."""
|
|
66
66
|
outcome = getattr(result, "outcome", None)
|
|
@@ -72,9 +72,9 @@ def _stop_reason(result: Any) -> str:
|
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
def _tool_kind(action_name: str) -> str:
|
|
75
|
-
if action_name.startswith("
|
|
75
|
+
if action_name.startswith("read"):
|
|
76
76
|
return "read"
|
|
77
|
-
if action_name in ("
|
|
77
|
+
if action_name in ("write", "edit"):
|
|
78
78
|
return "edit"
|
|
79
79
|
if action_name.startswith("exec_") or "command" in action_name:
|
|
80
80
|
return "execute"
|
|
@@ -123,12 +123,29 @@ class _AgentHarness:
|
|
|
123
123
|
config_store: ConfigStore | None = None,
|
|
124
124
|
cwd: Path | None = None,
|
|
125
125
|
subagents: bool = False,
|
|
126
|
-
|
|
126
|
+
approval_mode: str | None = None,
|
|
127
|
+
allow_dangerous: bool = False,
|
|
128
|
+
permission_timeout_s: float | None = None,
|
|
127
129
|
) -> None:
|
|
128
130
|
self._conn = conn
|
|
129
131
|
self._config_store = config_store or ConfigStore()
|
|
130
132
|
self._cwd = cwd or Path.cwd()
|
|
131
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
|
|
139
|
+
self._permission_timeout_from_config = permission_timeout_s is None
|
|
140
|
+
if permission_timeout_s is None:
|
|
141
|
+
try:
|
|
142
|
+
permission_timeout_s = (
|
|
143
|
+
self._config_store.read()
|
|
144
|
+
.runtime_settings.permission_timeout_s
|
|
145
|
+
)
|
|
146
|
+
except AttributeError:
|
|
147
|
+
# Lightweight test/fake stores may only implement resolve().
|
|
148
|
+
permission_timeout_s = BUILTIN_SETTINGS.permission_timeout_s
|
|
132
149
|
self._permission_timeout_s = permission_timeout_s
|
|
133
150
|
self._sessions: dict[str, _ManagedSession] = {}
|
|
134
151
|
self._new_session_lock = asyncio.Lock()
|
|
@@ -171,16 +188,23 @@ class _AgentHarness:
|
|
|
171
188
|
resolved = self._config_store.resolve()
|
|
172
189
|
except ConfigError as exc:
|
|
173
190
|
raise RequestError.internal_error(str(exc)) from exc
|
|
191
|
+
if self._permission_timeout_from_config:
|
|
192
|
+
self._permission_timeout_s = (
|
|
193
|
+
resolved.runtime_settings.permission_timeout_s
|
|
194
|
+
)
|
|
174
195
|
store = LocalConversationStore(
|
|
175
196
|
self._config_store.root / "sessions"
|
|
176
197
|
)
|
|
198
|
+
effective_mode = self._approval_mode or resolved.approval_mode
|
|
199
|
+
effective_approval = ApprovalMode(effective_mode)
|
|
177
200
|
runtime = build_runtime(
|
|
178
201
|
workspace,
|
|
179
202
|
provider_config=resolved,
|
|
180
|
-
approve_mode=
|
|
203
|
+
approve_mode=effective_approval,
|
|
181
204
|
stream=True,
|
|
182
205
|
event_handler=None,
|
|
183
206
|
approval_callback=self._make_approval(resolved),
|
|
207
|
+
file_access_callback=self._make_file_access_approval(),
|
|
184
208
|
)
|
|
185
209
|
try:
|
|
186
210
|
session = runtime.create_session(
|
|
@@ -190,7 +214,7 @@ class _AgentHarness:
|
|
|
190
214
|
),
|
|
191
215
|
policy=SessionPolicy(
|
|
192
216
|
allow_write=True,
|
|
193
|
-
allow_dangerous=
|
|
217
|
+
allow_dangerous=self._allow_dangerous,
|
|
194
218
|
max_parallel_actions=1,
|
|
195
219
|
),
|
|
196
220
|
conversation_store=store,
|
|
@@ -203,12 +227,13 @@ class _AgentHarness:
|
|
|
203
227
|
session_id, session, runtime
|
|
204
228
|
)
|
|
205
229
|
logger.info(
|
|
206
|
-
"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)",
|
|
207
231
|
session_id,
|
|
208
232
|
workspace,
|
|
209
233
|
resolved.name,
|
|
210
234
|
resolved.model,
|
|
211
|
-
|
|
235
|
+
effective_mode,
|
|
236
|
+
self._allow_dangerous,
|
|
212
237
|
)
|
|
213
238
|
return NewSessionResponse(session_id=session_id)
|
|
214
239
|
|
|
@@ -309,6 +334,52 @@ class _AgentHarness:
|
|
|
309
334
|
|
|
310
335
|
return approve
|
|
311
336
|
|
|
337
|
+
def _make_file_access_approval(self) -> Any:
|
|
338
|
+
async def approve(
|
|
339
|
+
context: Any,
|
|
340
|
+
request: ExternalFileReadRequest,
|
|
341
|
+
) -> ExternalFileReadDecision:
|
|
342
|
+
managed = self._managed(context.session_id)
|
|
343
|
+
started = helpers.start_tool_call(
|
|
344
|
+
tool_call_id=f"external-read-{request.request_id}",
|
|
345
|
+
title=f"Read external file {request.path}",
|
|
346
|
+
kind="read",
|
|
347
|
+
status="pending",
|
|
348
|
+
)
|
|
349
|
+
await self._notify_update(managed, started)
|
|
350
|
+
options = [
|
|
351
|
+
PermissionOption(
|
|
352
|
+
option_id="allow", name="Allow once", kind="allow_once"
|
|
353
|
+
),
|
|
354
|
+
PermissionOption(
|
|
355
|
+
option_id="reject", name="Reject", kind="reject_once"
|
|
356
|
+
),
|
|
357
|
+
]
|
|
358
|
+
approved = False
|
|
359
|
+
try:
|
|
360
|
+
response = await asyncio.wait_for(
|
|
361
|
+
self._conn.request_permission(
|
|
362
|
+
session_id=managed.session_id,
|
|
363
|
+
tool_call=started,
|
|
364
|
+
options=options,
|
|
365
|
+
),
|
|
366
|
+
timeout=self._permission_timeout_s,
|
|
367
|
+
)
|
|
368
|
+
except asyncio.TimeoutError:
|
|
369
|
+
logger.warning(
|
|
370
|
+
"external read permission timed out after %.0fs → deny",
|
|
371
|
+
self._permission_timeout_s,
|
|
372
|
+
)
|
|
373
|
+
else:
|
|
374
|
+
outcome = response.outcome
|
|
375
|
+
approved = (
|
|
376
|
+
getattr(outcome, "outcome", None) != "cancelled"
|
|
377
|
+
and getattr(outcome, "option_id", None) == "allow"
|
|
378
|
+
)
|
|
379
|
+
return ExternalFileReadDecision(request.request_id, approved)
|
|
380
|
+
|
|
381
|
+
return approve
|
|
382
|
+
|
|
312
383
|
# ----- event-stream translation (native port 5 → session/update) -----
|
|
313
384
|
|
|
314
385
|
async def _notify_update(self, managed: _ManagedSession, update: Any) -> None:
|
|
@@ -459,6 +530,8 @@ def build_harness(
|
|
|
459
530
|
config_store: ConfigStore | None = None,
|
|
460
531
|
cwd: Path | None = None,
|
|
461
532
|
subagents: bool = False,
|
|
533
|
+
approval_mode: str | None = None,
|
|
534
|
+
allow_dangerous: bool = False,
|
|
462
535
|
) -> _AgentHarness:
|
|
463
536
|
"""Factory used by ``__main__`` and tests."""
|
|
464
537
|
return _AgentHarness(
|
|
@@ -466,4 +539,6 @@ def build_harness(
|
|
|
466
539
|
config_store=config_store,
|
|
467
540
|
cwd=cwd,
|
|
468
541
|
subagents=subagents,
|
|
542
|
+
approval_mode=approval_mode,
|
|
543
|
+
allow_dangerous=allow_dangerous,
|
|
469
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 ("
|
|
46
|
-
verb = "Edit" if name == "
|
|
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 == "
|
|
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 ("
|
|
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)
|
|
@@ -4,6 +4,8 @@ from dataclasses import dataclass, field
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
+
from ..config import BUILTIN_SETTINGS
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
@dataclass(frozen=True)
|
|
9
11
|
class BenchmarkFile:
|
|
@@ -59,9 +61,9 @@ class BenchmarkRunConfig:
|
|
|
59
61
|
limit: int | None = None
|
|
60
62
|
case_ids: tuple[str, ...] = ()
|
|
61
63
|
concurrency: int = 1
|
|
62
|
-
max_steps: int =
|
|
63
|
-
step_timeout_s: float | None =
|
|
64
|
-
total_timeout_s: float | None =
|
|
64
|
+
max_steps: int = BUILTIN_SETTINGS.benchmark.max_steps
|
|
65
|
+
step_timeout_s: float | None = BUILTIN_SETTINGS.benchmark.step_timeout_s
|
|
66
|
+
total_timeout_s: float | None = BUILTIN_SETTINGS.benchmark.total_timeout_s
|
|
65
67
|
enable_subagents: bool = False
|
|
66
68
|
enable_background_tasks: bool = False
|
|
67
69
|
seed: int | None = None
|
|
@@ -20,7 +20,13 @@
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
22
|
import typer
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
# Typer 0.27 vendors Click to keep its exception types aligned with its
|
|
26
|
+
# renderer; older Typer releases use the public Click package directly.
|
|
27
|
+
from typer.core import _click
|
|
28
|
+
except ImportError: # pragma: no cover - compatibility with older Typer
|
|
29
|
+
import click as _click
|
|
24
30
|
|
|
25
31
|
from .._version import __version__
|
|
26
32
|
from ._shared import console
|
|
@@ -46,9 +52,10 @@ class DefaultShellGroup(typer.core.TyperGroup):
|
|
|
46
52
|
if args:
|
|
47
53
|
first = args[0]
|
|
48
54
|
if first == "shell":
|
|
49
|
-
raise UsageError(
|
|
55
|
+
raise _click.exceptions.UsageError(
|
|
50
56
|
"`mindcode shell` 已移除;请改用 "
|
|
51
|
-
"`mindcode [PROMPT] [OPTIONS]`"
|
|
57
|
+
"`mindcode [PROMPT] [OPTIONS]`",
|
|
58
|
+
ctx,
|
|
52
59
|
)
|
|
53
60
|
if (
|
|
54
61
|
first in PUBLIC_SUBCOMMANDS
|
|
@@ -86,15 +86,22 @@ def run(
|
|
|
86
86
|
"--benchmark-root",
|
|
87
87
|
help="benchmark case 根目录",
|
|
88
88
|
),
|
|
89
|
-
max_steps: int = typer.Option(
|
|
89
|
+
max_steps: int | None = typer.Option(
|
|
90
|
+
None, "--max-steps", help="每 case 最大 step"
|
|
91
|
+
),
|
|
90
92
|
total_timeout: float | None = typer.Option(
|
|
91
|
-
|
|
93
|
+
None,
|
|
92
94
|
"--total-timeout",
|
|
93
|
-
help="每 case
|
|
95
|
+
help="每 case 总超时秒数,0 表示禁用",
|
|
94
96
|
),
|
|
95
97
|
) -> None:
|
|
96
98
|
run_id = f"{suite}-{time.strftime('%Y%m%d-%H%M%S')}"
|
|
97
99
|
try:
|
|
100
|
+
total_timeout_disabled = total_timeout == 0
|
|
101
|
+
if total_timeout is not None and total_timeout < 0:
|
|
102
|
+
raise ValueError("total timeout 必须为 0 或大于 0")
|
|
103
|
+
if total_timeout_disabled:
|
|
104
|
+
total_timeout = None
|
|
98
105
|
approval_override = (
|
|
99
106
|
ApprovalMode(approve_mode).value
|
|
100
107
|
if approve_mode is not None
|
|
@@ -106,6 +113,11 @@ def run(
|
|
|
106
113
|
approval_override=approval_override,
|
|
107
114
|
)
|
|
108
115
|
selected_approve_mode = resolved.approval_mode
|
|
116
|
+
benchmark_defaults = resolved.runtime_settings.benchmark
|
|
117
|
+
if max_steps is None:
|
|
118
|
+
max_steps = benchmark_defaults.max_steps
|
|
119
|
+
if total_timeout is None and not total_timeout_disabled:
|
|
120
|
+
total_timeout = benchmark_defaults.total_timeout_s
|
|
109
121
|
output_root = Path(output).expanduser()
|
|
110
122
|
run_output_dir = _run_output_dir(output_root, run_id)
|
|
111
123
|
run_config = BenchmarkRunConfig(
|
|
@@ -6,6 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
import typer
|
|
8
8
|
|
|
9
|
+
from ...config import BUILTIN_SETTINGS
|
|
9
10
|
from ...policy import ApprovalMode
|
|
10
11
|
from .._shared import console
|
|
11
12
|
from ..shell import run_shell
|
|
@@ -41,15 +42,25 @@ def chat(
|
|
|
41
42
|
False, "--no-stream", help="关闭流式输出"
|
|
42
43
|
),
|
|
43
44
|
max_steps: int | None = typer.Option(
|
|
44
|
-
None,
|
|
45
|
+
None,
|
|
46
|
+
"--max-steps",
|
|
47
|
+
help=f"最大 step 数(默认 {BUILTIN_SETTINGS.one_shot.max_steps})",
|
|
45
48
|
),
|
|
46
49
|
step_timeout: float | None = typer.Option(
|
|
47
50
|
None,
|
|
48
51
|
"--step-timeout",
|
|
49
|
-
help=
|
|
52
|
+
help=(
|
|
53
|
+
"单步超时秒数(默认 "
|
|
54
|
+
f"{BUILTIN_SETTINGS.one_shot.step_timeout_s:g},必须大于 0)"
|
|
55
|
+
),
|
|
50
56
|
),
|
|
51
57
|
total_timeout: float | None = typer.Option(
|
|
52
|
-
None,
|
|
58
|
+
None,
|
|
59
|
+
"--total-timeout",
|
|
60
|
+
help=(
|
|
61
|
+
"总超时秒数,0 表示禁用(默认 "
|
|
62
|
+
f"{BUILTIN_SETTINGS.one_shot.total_timeout_s:g})"
|
|
63
|
+
),
|
|
53
64
|
),
|
|
54
65
|
) -> None:
|
|
55
66
|
"""发送单条消息,不进入交互模式。"""
|
|
@@ -78,13 +89,32 @@ def chat(
|
|
|
78
89
|
resume=resume,
|
|
79
90
|
stream=not no_stream,
|
|
80
91
|
one_shot_prompt=message,
|
|
81
|
-
max_steps=
|
|
92
|
+
max_steps=(
|
|
93
|
+
max_steps
|
|
94
|
+
if max_steps is not None
|
|
95
|
+
else BUILTIN_SETTINGS.one_shot.max_steps
|
|
96
|
+
),
|
|
82
97
|
step_timeout_s=(
|
|
83
|
-
step_timeout
|
|
98
|
+
step_timeout
|
|
99
|
+
if step_timeout is not None
|
|
100
|
+
else BUILTIN_SETTINGS.one_shot.step_timeout_s
|
|
84
101
|
),
|
|
85
102
|
total_timeout_s=None
|
|
86
103
|
if total_timeout == 0
|
|
87
|
-
else (
|
|
104
|
+
else (
|
|
105
|
+
total_timeout
|
|
106
|
+
if total_timeout is not None
|
|
107
|
+
else BUILTIN_SETTINGS.one_shot.total_timeout_s
|
|
108
|
+
),
|
|
109
|
+
settings_defaults=frozenset(
|
|
110
|
+
name
|
|
111
|
+
for name, value in (
|
|
112
|
+
("max_steps", max_steps),
|
|
113
|
+
("step_timeout_s", step_timeout),
|
|
114
|
+
("total_timeout_s", total_timeout),
|
|
115
|
+
)
|
|
116
|
+
if value is None
|
|
117
|
+
),
|
|
88
118
|
)
|
|
89
119
|
except ValueError as exc:
|
|
90
120
|
console.print(f"[red]配置错误:[/red] {exc}")
|
|
@@ -6,6 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
import typer
|
|
8
8
|
|
|
9
|
+
from ...config import BUILTIN_SETTINGS
|
|
9
10
|
from ...policy import ApprovalMode
|
|
10
11
|
from .._shared import console
|
|
11
12
|
from ..shell import run_shell
|
|
@@ -46,23 +47,34 @@ def shell(
|
|
|
46
47
|
no_stream: bool = typer.Option(
|
|
47
48
|
False, "--no-stream", help="关闭流式输出"
|
|
48
49
|
),
|
|
50
|
+
no_markdown: bool = typer.Option(
|
|
51
|
+
False, "--no-markdown", help="关闭交互消息的 Markdown 渲染"
|
|
52
|
+
),
|
|
49
53
|
subagents: bool = typer.Option(
|
|
50
54
|
False,
|
|
51
55
|
"--subagents",
|
|
52
56
|
help="使用 master/coder 委派模式(await_result)",
|
|
53
57
|
),
|
|
54
58
|
max_steps: int | None = typer.Option(
|
|
55
|
-
None,
|
|
59
|
+
None,
|
|
60
|
+
"--max-steps",
|
|
61
|
+
help=f"最大 step 数(默认 {BUILTIN_SETTINGS.one_shot.max_steps})",
|
|
56
62
|
),
|
|
57
63
|
step_timeout: float | None = typer.Option(
|
|
58
64
|
None,
|
|
59
65
|
"--step-timeout",
|
|
60
|
-
help=
|
|
66
|
+
help=(
|
|
67
|
+
"单步超时秒数(交互默认不限制;one-shot 默认 "
|
|
68
|
+
f"{BUILTIN_SETTINGS.one_shot.step_timeout_s:g},必须大于 0)"
|
|
69
|
+
),
|
|
61
70
|
),
|
|
62
71
|
total_timeout: float | None = typer.Option(
|
|
63
72
|
None,
|
|
64
73
|
"--total-timeout",
|
|
65
|
-
help=
|
|
74
|
+
help=(
|
|
75
|
+
"总超时秒数,0 表示禁用(交互默认禁用;一次性/子代理默认 "
|
|
76
|
+
f"{BUILTIN_SETTINGS.one_shot.total_timeout_s:g})"
|
|
77
|
+
),
|
|
66
78
|
),
|
|
67
79
|
) -> None:
|
|
68
80
|
"""启动顶层 `mindcode` 交互或 one-shot 模式。"""
|
|
@@ -95,7 +107,9 @@ def shell(
|
|
|
95
107
|
selected_total_timeout = None
|
|
96
108
|
else:
|
|
97
109
|
selected_total_timeout = (
|
|
98
|
-
total_timeout
|
|
110
|
+
total_timeout
|
|
111
|
+
if total_timeout is not None
|
|
112
|
+
else BUILTIN_SETTINGS.one_shot.total_timeout_s
|
|
99
113
|
)
|
|
100
114
|
|
|
101
115
|
try:
|
|
@@ -107,15 +121,37 @@ def shell(
|
|
|
107
121
|
session_id=session,
|
|
108
122
|
resume=resume,
|
|
109
123
|
stream=not no_stream,
|
|
124
|
+
markdown_enabled=False if no_markdown else None,
|
|
110
125
|
subagents=subagents,
|
|
111
126
|
one_shot_prompt=prompt,
|
|
112
|
-
max_steps=
|
|
127
|
+
max_steps=(
|
|
128
|
+
max_steps
|
|
129
|
+
if max_steps is not None
|
|
130
|
+
else (
|
|
131
|
+
BUILTIN_SETTINGS.interactive.max_steps
|
|
132
|
+
if prompt is None
|
|
133
|
+
else BUILTIN_SETTINGS.one_shot.max_steps
|
|
134
|
+
)
|
|
135
|
+
),
|
|
113
136
|
step_timeout_s=(
|
|
114
137
|
step_timeout
|
|
115
138
|
if step_timeout is not None
|
|
116
|
-
else (
|
|
139
|
+
else (
|
|
140
|
+
BUILTIN_SETTINGS.interactive.step_timeout_s
|
|
141
|
+
if prompt is None
|
|
142
|
+
else BUILTIN_SETTINGS.one_shot.step_timeout_s
|
|
143
|
+
)
|
|
117
144
|
),
|
|
118
145
|
total_timeout_s=selected_total_timeout,
|
|
146
|
+
settings_defaults=frozenset(
|
|
147
|
+
name
|
|
148
|
+
for name, value in (
|
|
149
|
+
("max_steps", max_steps),
|
|
150
|
+
("step_timeout_s", step_timeout),
|
|
151
|
+
("total_timeout_s", total_timeout),
|
|
152
|
+
)
|
|
153
|
+
if value is None
|
|
154
|
+
),
|
|
119
155
|
)
|
|
120
156
|
except ValueError as exc:
|
|
121
157
|
console.print(f"[red]配置错误:[/red] {exc}")
|