From 8943af448452babe9719cf0c6795e656fc1c8425 Mon Sep 17 00:00:00 2001 From: Agent Date: Thu, 16 Apr 2026 16:00:17 +0000 Subject: [PATCH 1/2] fix: bug: hire-an-agent TOML editor corrupts existing [agents.X] block on re-run (#886) --- lib/hire-agent.sh | 67 +++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/lib/hire-agent.sh b/lib/hire-agent.sh index 149845b..45d0b0b 100644 --- a/lib/hire-agent.sh +++ b/lib/hire-agent.sh @@ -535,7 +535,11 @@ EOF local interval="${poll_interval:-60}" echo " Writing [agents.${section_name}] to ${toml_file}..." python3 -c ' -import sys, re, pathlib +import sys +import tomllib +import tomli_w +import re +import pathlib toml_path = sys.argv[1] section_name = sys.argv[2] @@ -548,38 +552,39 @@ poll_interval = sys.argv[7] p = pathlib.Path(toml_path) text = p.read_text() -# Build the new section -new_section = f""" -[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 = {poll_interval} -""" +# Step 1: Remove any commented-out [agents.X] blocks (they cause parse issues) +# Match # [agents.section_name] followed by lines that are not section headers +# Use negative lookahead to stop before a real section header (# [ or [) +commented_pattern = rf"(?:^|\n)# \[agents\.{re.escape(section_name)}\](?:\n(?!# \[|\[)[^\n]*)*" +text = re.sub(commented_pattern, "", text, flags=re.DOTALL) -# Check if section already exists and replace it -pattern = rf"\[agents\.{re.escape(section_name)}\][^\[]*" -if re.search(pattern, text): - text = re.sub(pattern, new_section.strip() + "\n", text) -else: - # Remove commented-out example [agents.llama] block if present - 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 +# Step 2: Parse TOML with tomllib +try: + data = tomllib.loads(text) +except tomllib.TOMLDecodeError as e: + print(f"Error: Invalid TOML in {toml_path}: {e}", file=sys.stderr) + sys.exit(1) -p.write_text(text) +# Step 3: Ensure agents table exists +if "agents" not in data: + data["agents"] = {} + +# Step 4: Update the specific agent section +data["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 +output = tomli_w.dumps(data) + +# Step 6: Write back +p.write_text(output) ' "$toml_file" "$section_name" "$local_model" "$model" "$agent_name" "$role" "$interval" echo " Agent config written to TOML" From cf99bdc51e94db98de2ff6b3c5923356fce9da97 Mon Sep 17 00:00:00 2001 From: Agent Date: Thu, 16 Apr 2026 16:21:07 +0000 Subject: [PATCH 2/2] fix: add tomlkit to Dockerfile for comment-preserving TOML editing (#886) --- docker/agents/Dockerfile | 2 +- lib/hire-agent.sh | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/docker/agents/Dockerfile b/docker/agents/Dockerfile index 2939230..1bcba89 100644 --- a/docker/agents/Dockerfile +++ b/docker/agents/Dockerfile @@ -2,7 +2,7 @@ FROM debian:bookworm-slim 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 \ - && pip3 install --break-system-packages networkx \ + && pip3 install --break-system-packages networkx tomlkit \ && rm -rf /var/lib/apt/lists/* # Pre-built binaries (copied from docker/agents/bin/) diff --git a/lib/hire-agent.sh b/lib/hire-agent.sh index 45d0b0b..170389f 100644 --- a/lib/hire-agent.sh +++ b/lib/hire-agent.sh @@ -536,8 +536,7 @@ EOF echo " Writing [agents.${section_name}] to ${toml_file}..." python3 -c ' import sys -import tomllib -import tomli_w +import tomlkit import re import pathlib @@ -558,19 +557,19 @@ text = p.read_text() commented_pattern = rf"(?:^|\n)# \[agents\.{re.escape(section_name)}\](?:\n(?!# \[|\[)[^\n]*)*" text = re.sub(commented_pattern, "", text, flags=re.DOTALL) -# Step 2: Parse TOML with tomllib +# Step 2: Parse TOML with tomlkit (preserves comments and formatting) try: - data = tomllib.loads(text) -except tomllib.TOMLDecodeError as e: + doc = tomlkit.parse(text) +except Exception as e: print(f"Error: Invalid TOML in {toml_path}: {e}", file=sys.stderr) sys.exit(1) # Step 3: Ensure agents table exists -if "agents" not in data: - data["agents"] = {} +if "agents" not in doc: + doc.add("agents", tomlkit.table()) # Step 4: Update the specific agent section -data["agents"][section_name] = { +doc["agents"][section_name] = { "base_url": base_url, "model": model, "api_key": "sk-no-key-required", @@ -580,8 +579,8 @@ data["agents"][section_name] = { "poll_interval": int(poll_interval), } -# Step 5: Serialize back to TOML -output = tomli_w.dumps(data) +# Step 5: Serialize back to TOML (preserves comments) +output = tomlkit.dumps(doc) # Step 6: Write back p.write_text(output)