fix: bug: hire-an-agent TOML editor corrupts existing [agents.X] block on re-run (#886) #891

Merged
dev-qwen merged 2 commits from fix/issue-886 into main 2026-04-16 16:31:21 +00:00
2 changed files with 36 additions and 32 deletions

View file

@ -2,7 +2,7 @@ FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
bash curl git jq tmux python3 python3-pip openssh-client ca-certificates age shellcheck procps gosu \ bash curl git jq tmux python3 python3-pip openssh-client ca-certificates age shellcheck procps gosu \
&& pip3 install --break-system-packages networkx \ && pip3 install --break-system-packages networkx tomlkit \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Pre-built binaries (copied from docker/agents/bin/) # Pre-built binaries (copied from docker/agents/bin/)

View file

@ -535,7 +535,10 @@ EOF
local interval="${poll_interval:-60}" local interval="${poll_interval:-60}"
echo " Writing [agents.${section_name}] to ${toml_file}..." echo " Writing [agents.${section_name}] to ${toml_file}..."
python3 -c ' python3 -c '
import sys, re, pathlib import sys
import tomlkit
import re
import pathlib
toml_path = sys.argv[1] toml_path = sys.argv[1]
section_name = sys.argv[2] section_name = sys.argv[2]
@ -548,38 +551,39 @@ poll_interval = sys.argv[7]
p = pathlib.Path(toml_path) p = pathlib.Path(toml_path)
text = p.read_text() text = p.read_text()
# Build the new section # Step 1: Remove any commented-out [agents.X] blocks (they cause parse issues)
new_section = f""" # Match # [agents.section_name] followed by lines that are not section headers
[agents.{section_name}] # Use negative lookahead to stop before a real section header (# [ or [)
base_url = "{base_url}" commented_pattern = rf"(?:^|\n)# \[agents\.{re.escape(section_name)}\](?:\n(?!# \[|\[)[^\n]*)*"
model = "{model}" text = re.sub(commented_pattern, "", text, flags=re.DOTALL)
api_key = "sk-no-key-required"
roles = ["{role}"]
forge_user = "{agent_name}"
compact_pct = 60
poll_interval = {poll_interval}
"""
# Check if section already exists and replace it # Step 2: Parse TOML with tomlkit (preserves comments and formatting)
pattern = rf"\[agents\.{re.escape(section_name)}\][^\[]*" try:
if re.search(pattern, text): doc = tomlkit.parse(text)
text = re.sub(pattern, new_section.strip() + "\n", text) except Exception as e:
else: print(f"Error: Invalid TOML in {toml_path}: {e}", file=sys.stderr)
# Remove commented-out example [agents.llama] block if present sys.exit(1)
text = re.sub(
r"\n# Local-model agents \(optional\).*?(?=\n# \[mirrors\]|\n\[mirrors\]|\Z)",
"",
text,
flags=re.DOTALL,
)
# Append before [mirrors] if it exists, otherwise at end
mirrors_match = re.search(r"\n(# )?\[mirrors\]", text)
if mirrors_match:
text = text[:mirrors_match.start()] + "\n" + new_section + text[mirrors_match.start():]
else:
text = text.rstrip() + "\n" + new_section
p.write_text(text) # Step 3: Ensure agents table exists
if "agents" not in doc:
doc.add("agents", tomlkit.table())
# Step 4: Update the specific agent section
doc["agents"][section_name] = {
"base_url": base_url,
"model": model,
"api_key": "sk-no-key-required",
"roles": [role],
"forge_user": agent_name,
"compact_pct": 60,
"poll_interval": int(poll_interval),
}
# Step 5: Serialize back to TOML (preserves comments)
output = tomlkit.dumps(doc)
# Step 6: Write back
p.write_text(output)
' "$toml_file" "$section_name" "$local_model" "$model" "$agent_name" "$role" "$interval" ' "$toml_file" "$section_name" "$local_model" "$model" "$agent_name" "$role" "$interval"
echo " Agent config written to TOML" echo " Agent config written to TOML"