feat: add official Codex skill and harden task execution

Install and update artemis-use with task diagnostics and plugin guidance. Preserve cross-device file receipts, review canonical reports, guard stage transitions and reduce small history summaries. Improve exploration lifecycle, request recovery and browser live connections.
This commit is contained in:
chenmingxuan committed 2026-10-18 14:18:37 +08:00
1 parent 2328a4df0c
commit e1ea099592
128 files changed
+4944 -700

No files matched your search

+24 -102
View File
@@ -104,122 +104,44 @@ def _codex_toml_block(server_config: dict) -> str:
def _merge_codex_toml(file_path: Path, server_config: dict) -> bool:
"""Adds or updates Artemis in Codex config.toml while preserving unrelated settings."""
from artemis.integrations.codex_mcp import refresh_codex
try:
file_path.parent.mkdir(parents=True, exist_ok=True)
existing = file_path.read_text(encoding="utf-8") if file_path.exists() else ""
managed_block = _codex_toml_block(server_config)
begin_marker = "# BEGIN ARTEMIS MCP CONFIG"
end_marker = "# END ARTEMIS MCP CONFIG"
if begin_marker in existing and end_marker in existing:
managed_pattern = re.compile(
rf"(?ms)^{re.escape(begin_marker)}\n.*?^{re.escape(end_marker)}\n?"
)
# Windows paths contain backslashes; a replacement string would
# interpret them as regex escapes on a second installation.
updated = managed_pattern.sub(lambda _: managed_block, existing, count=1)
else:
# Remove an older, unmanaged Artemis table before adding the managed block.
kept_lines: list[str] = []
skipping_artemis = False
header_pattern = re.compile(r"^\s*\[{1,2}\s*(.+?)\s*\]{1,2}\s*(?:#.*)?$")
for line in existing.splitlines(keepends=True):
header_match = header_pattern.match(line.rstrip("\r\n"))
if header_match:
normalized = re.sub(r"[\s\"']", "", header_match.group(1)).lower()
skipping_artemis = normalized == "mcp_servers.artemis" or normalized.startswith(
"mcp_servers.artemis."
)
if not skipping_artemis:
kept_lines.append(line)
preserved = "".join(kept_lines).rstrip()
updated = f"{preserved}\n\n{managed_block}" if preserved else managed_block
# Never replace a user's config with invalid TOML.
tomllib.loads(updated)
if updated != existing:
file_path.write_text(updated, encoding="utf-8")
result = refresh_codex(
file_path,
Path(server_config["cwd"]),
server_config["command"],
ARTEMIS_MCP_TOOLS,
template=server_config,
install_missing=True,
)
if result is None:
logger.warning("保留自定义 Codex MCP 连接;请手动核对 ARTEMIS 配置。")
return True
except Exception as e:
if file_path.exists():
try:
backup_path = file_path.with_suffix(file_path.suffix + ".bak")
backup_path.write_text(file_path.read_text(encoding="utf-8"), encoding="utf-8")
logger.warning(
f"Could not update {file_path}; backup created at {backup_path}: {e}"
)
except (OSError, UnicodeDecodeError) as backup_exc:
logger.warning(
f"Could not update {file_path} ({e}); backup also failed: {backup_exc}"
)
else:
logger.warning(f"Could not create Codex MCP config file {file_path}: {e}")
return False
def _inject_rules_block(file_path: Path, content: str) -> bool:
"""Injects or updates a managed ARTEMIS block in a global rules file without overwriting user content."""
try:
file_path.parent.mkdir(parents=True, exist_ok=True)
begin_marker = "<!-- BEGIN ARTEMIS MOBILE TESTING RULES -->"
end_marker = "<!-- END ARTEMIS MOBILE TESTING RULES -->"
new_block = f"{begin_marker}\n{content.strip()}\n{end_marker}\n"
existing = ""
if file_path.exists():
try:
existing = file_path.read_text(encoding="utf-8")
except Exception:
existing = ""
if begin_marker in existing and end_marker in existing:
prefix = existing.split(begin_marker)[0]
suffix = existing.split(end_marker)[1]
updated = f"{prefix.rstrip()}\n\n{new_block}\n{suffix.lstrip()}".strip() + "\n"
else:
updated = f"{existing.rstrip()}\n\n{new_block}".strip() + "\n"
if existing == updated:
return True
file_path.write_text(updated, encoding="utf-8")
return True
except Exception as e:
logger.warning(f"Could not inject rules block into {file_path}: {e}")
except (OSError, ValueError, TypeError):
logger.warning("Codex MCP 同步失败,原配置已保留;请检查 TOML 格式或文件权限。")
return False
def install_rules(client: str, project_root: str) -> list[str]:
"""Install testing rules only into the Codex instructions file."""
"""Compatibility entry: install the official Skill and migrate old marked rules."""
if client != "codex":
raise ValueError("Only Codex MCP configuration is supported")
rules_src = Path(project_root) / "mcp_server" / "rules.md"
if not rules_src.exists():
rules_src = Path(__file__).resolve().parents[4] / "mcp_server" / "rules.md"
try:
raw_rules = rules_src.read_text(encoding="utf-8").strip()
except OSError:
logger.warning(f"Could not read rules file {rules_src}")
return []
codex_home = Path(os.environ.get("CODEX_HOME") or (Path.home() / ".codex"))
override_file = codex_home / "AGENTS.override.md"
agents_file = (
override_file
if override_file.exists() and override_file.read_text(encoding="utf-8").strip()
else codex_home / "AGENTS.md"
)
return [str(agents_file)] if _inject_rules_block(agents_file, raw_rules) else []
from artemis.integrations.codex_skill import sync_skill_safely
result = sync_skill_safely(Path(project_root))
return [result["path"]] if result.get("path") else []
def install_mcp_config(client: str, python_exe: str, project_root: str) -> list[str]:
"""Install ARTEMIS configuration and rules only for Codex."""
"""Install Codex MCP and the official artemis-use Skill."""
server_cfg = _get_config_snippet(client, python_exe, project_root)["mcp_servers"]["artemis"]
codex_home = Path(os.environ.get("CODEX_HOME") or (Path.home() / ".codex"))
skill_paths = install_rules(client, project_root)
codex_path = codex_home / "config.toml"
if not _merge_codex_toml(codex_path, server_cfg):
raise typer.Exit(1)
return [str(codex_path), *install_rules(client, project_root)]
return [str(codex_path), *skill_paths]
def mcp_command(
@@ -258,7 +180,7 @@ def mcp_command(
typer.Option(
"--install",
"-i",
help="Install ARTEMIS MCP configuration and testing rules for Codex: --install codex.",
help="Install ARTEMIS MCP configuration and official artemis-use Skill for Codex: --install codex.",
),
] = None,
generate_config: Annotated[
@@ -283,7 +205,7 @@ def mcp_command(
raise typer.Exit(1)
installed_paths = install_mcp_config(client, python_exe, project_root)
console.print(
"[bold green]✔ Successfully installed ARTEMIS MCP server configuration & rules to:[/bold green]"
"[bold green]✔ Successfully installed ARTEMIS MCP server configuration & artemis-use Skill to:[/bold green]"
)
for path in installed_paths:
console.print(f" • [cyan]{path}[/cyan]")