feat(PHASE-03): 环境感知 + 稳定性约束 + Windows 防重复试错 + max_turns=12

This commit is contained in:
hc 2026-04-10 20:34:56 +08:00
parent 1d66019529
commit 587331cbb8
4 changed files with 39 additions and 4 deletions

View File

@ -21,6 +21,10 @@
4. 使用工具后重新判断结果。 4. 使用工具后重新判断结果。
5. 持续循环,直到得到最终答案或出现必须由用户补充的信息。 5. 持续循环,直到得到最终答案或出现必须由用户补充的信息。
当前 harness 是极简实现,优先最小动作,不做不必要的重复试错。
行动时必须以运行时注入的环境信息为准特别是平台、shell、工作目录和可用工具列表。
未明确说明时,使用以下默认值: 未明确说明时,使用以下默认值:
- 工作目录:当前进程目录 - 工作目录:当前进程目录
@ -35,6 +39,12 @@
- `Read`:用于读取文件内容。 - `Read`:用于读取文件内容。
- `Glob`:用于按模式查找文件。 - `Glob`:用于按模式查找文件。
- `Bash`:用于执行必须通过 shell 完成的最小命令。 - `Bash`:用于执行必须通过 shell 完成的最小命令。
- 只有在 Bash 确实必要时才使用 Bash。
- Windows 环境下优先使用兼容写法,不默认使用 `cat <<EOF`、`ls -la` 等 Unix 风格写法。
- 同一写入或创建目标,最多尝试 2 种不同 Bash 方案。
- 如果两次 Bash 方案失败,应立即收敛:
- 先检查路径、文件状态、shell 兼容性。
- 如仍不确定,则询问用户,而不是继续盲试。
- 不能虚构工具输出。 - 不能虚构工具输出。
- 不能在未验证前声称文件存在、命令成功或修改已生效。 - 不能在未验证前声称文件存在、命令成功或修改已生效。
@ -42,5 +52,5 @@
- 对不确定性保持诚实。 - 对不确定性保持诚实。
- 需要时引用具体文件或命令。 - 需要时引用具体文件或命令。
- 默认保持简短,除非用户要求展开 - 输出保持简短直接,不夸大成功状态
- 若受阻,只询问当前缺失的关键信息。 - 若受阻,只询问当前缺失的关键信息。

View File

@ -20,6 +20,7 @@
- 重构时: - 重构时:
- 先找全部用法 - 先找全部用法
- 避免修改无关文件 - 避免修改无关文件
- Windows 下先验证 shell 兼容性,再选择命令写法
## 沟通风格 ## 沟通风格

View File

@ -24,7 +24,7 @@ packages = ["src/cc_slim"]
[tool.cc_slim] [tool.cc_slim]
provider = "openai" provider = "openai"
model = "gpt-4.1-mini" model = "gpt-4.1-mini"
max_turns = 8 max_turns = 12
[dependency-groups] [dependency-groups]
dev = [ dev = [

View File

@ -2,6 +2,8 @@ from __future__ import annotations
import json import json
import os import os
import platform
import sys
import tomllib import tomllib
from dataclasses import dataclass from dataclasses import dataclass
from json import JSONDecodeError from json import JSONDecodeError
@ -20,7 +22,7 @@ class Config:
model: str model: str
api_key: str api_key: str
base_url: str | None base_url: str | None
max_turns: int = 8 max_turns: int = 12
def resolve_config(workspace: Path, cli: dict[str, Any]) -> Config: def resolve_config(workspace: Path, cli: dict[str, Any]) -> Config:
@ -40,7 +42,7 @@ def resolve_config(workspace: Path, cli: dict[str, Any]) -> Config:
"", "",
) )
base_url = _pick(cli.get("base_url"), os.getenv("CC_SLIM_BASE_URL"), file_cfg.get("base_url"), None) base_url = _pick(cli.get("base_url"), os.getenv("CC_SLIM_BASE_URL"), file_cfg.get("base_url"), None)
max_turns_raw = _pick(cli.get("max_turns"), os.getenv("CC_SLIM_MAX_TURNS"), file_cfg.get("max_turns"), 8) max_turns_raw = _pick(cli.get("max_turns"), os.getenv("CC_SLIM_MAX_TURNS"), file_cfg.get("max_turns"), 12)
if not api_key: if not api_key:
raise ValueError("缺少 API key请通过 CLI、环境变量或 .cc-slim.toml 提供。") raise ValueError("缺少 API key请通过 CLI、环境变量或 .cc-slim.toml 提供。")
@ -120,6 +122,8 @@ class Agent:
def _build_system_prompt(self, workspace: Path) -> str: def _build_system_prompt(self, workspace: Path) -> str:
parts: list[str] = [] parts: list[str] = []
parts.append(self._build_runtime_summary(workspace))
agents = workspace / "AGENTS.md" agents = workspace / "AGENTS.md"
if agents.exists(): if agents.exists():
parts.append(agents.read_text(encoding="utf-8")) parts.append(agents.read_text(encoding="utf-8"))
@ -131,6 +135,26 @@ class Agent:
return "\n\n".join(part.strip() for part in parts if part.strip()) return "\n\n".join(part.strip() for part in parts if part.strip())
def _build_runtime_summary(self, workspace: Path) -> str:
tool_names = ", ".join(self.tools.keys()) or "(none)"
shell_name = self._detect_shell()
return "\n".join(
[
"## 运行环境",
f"- 平台: {platform.system() or 'Unknown'}",
f"- sys.platform: {sys.platform}",
f"- shell: {shell_name}",
f"- workspace: {workspace}",
f"- 可用工具: {tool_names}",
"- 行动时必须以以上运行环境信息为准,不要默认套用 Unix/Linux 命令习惯。",
]
)
def _detect_shell(self) -> str:
if os.name == "nt":
return os.getenv("COMSPEC", "Windows shell (likely PowerShell or cmd.exe)")
return os.getenv("SHELL", "unknown shell")
def _call_openai(self) -> dict[str, Any]: def _call_openai(self) -> dict[str, Any]:
response = self.client.chat.completions.create( response = self.client.chat.completions.create(
model=self.config.model, model=self.config.model,