diff --git a/.env.example b/.env.example index 7e76ec2..c1c0b98 100644 --- a/.env.example +++ b/.env.example @@ -25,8 +25,16 @@ FORGE_URL=http://localhost:3000 # [CONFIG] local Forgejo instance # - FORGE_TOKEN_ = API token for REST calls (user identity via /api/v1/user) # - FORGE_PASS_ = password for git HTTP push (#361, Forgejo 11.x limitation) # -# Local-model agents (agents-llama) use FORGE_TOKEN_LLAMA / FORGE_PASS_LLAMA -# with FORGE_BOT_USER_LLAMA=dev-qwen to ensure correct attribution (#563). +# Local-model agents hired with `disinto hire-an-agent` are keyed by *agent +# name* (not role), so multiple local-model dev agents can coexist without +# colliding on credentials (#834). For an agent named `dev-qwen2` the vars are: +# - FORGE_TOKEN_DEV_QWEN2 +# - FORGE_PASS_DEV_QWEN2 +# Name conversion: tr 'a-z-' 'A-Z_' (lowercase→UPPER, hyphens→underscores). +# The compose generator looks these up via the agent's `forge_user` field in +# the project TOML. The pre-existing `dev-qwen` llama agent uses +# FORGE_TOKEN_LLAMA / FORGE_PASS_LLAMA (kept for backwards-compat with the +# legacy `ENABLE_LLAMA_AGENT=1` single-agent path). FORGE_TOKEN= # [SECRET] dev-bot API token (default for all agents) FORGE_PASS= # [SECRET] dev-bot password for git HTTP push (#361) FORGE_TOKEN_LLAMA= # [SECRET] dev-qwen API token (for agents-llama) diff --git a/lib/generators.sh b/lib/generators.sh index 02af667..af08aa2 100644 --- a/lib/generators.sh +++ b/lib/generators.sh @@ -97,6 +97,13 @@ _generate_local_model_services() { POLL_INTERVAL) poll_interval_val="$value" ;; ---) if [ -n "$service_name" ] && [ -n "$base_url" ]; then + # Per-agent FORGE_TOKEN / FORGE_PASS lookup (#834 Gap 3). + # Two hired llama agents must not share the same Forgejo identity, + # so we key the env-var lookup by forge_user (which hire-agent.sh + # writes as the Forgejo username). Apply the same tr 'a-z-' 'A-Z_' + # convention as hire-agent.sh Gap 1 so the names match. + local user_upper + user_upper=$(echo "$forge_user" | tr 'a-z-' 'A-Z_') cat >> "$temp_file" <1 local-model agent is + # configured — without this, the second agent's volume entry would + # unterminate the sed expression). + local all_vols_escaped + all_vols_escaped=$(printf '%s' "$all_vols" | sed ':a;N;$!ba;s/\n/\\n/g') # Find the volumes section and add the new volumes - sed -i "/^volumes:/{n;:a;n;/^[a-z]/!{s/$/\n$all_vols/;b};ba}" "$temp_compose" + sed -i "/^volumes:/{n;:a;n;/^[a-z]/!{s/$/\n$all_vols_escaped/;b};ba}" "$temp_compose" fi mv "$temp_compose" "$compose_file" diff --git a/lib/hire-agent.sh b/lib/hire-agent.sh index 91d1fc8..49ab8ae 100644 --- a/lib/hire-agent.sh +++ b/lib/hire-agent.sh @@ -167,10 +167,14 @@ disinto_hire_an_agent() { echo "" echo "Step 1.5: Generating Forge token for '${agent_name}'..." - # Convert role to uppercase token variable name (e.g., architect -> FORGE_ARCHITECT_TOKEN) - local role_upper - role_upper=$(echo "$role" | tr '[:lower:]' '[:upper:]') - local token_var="FORGE_${role_upper}_TOKEN" + # Key per-agent credentials by *agent name*, not role (#834 Gap 1). + # Two agents with the same role (e.g. two `dev` agents) must not collide on + # FORGE__TOKEN — the compose generator looks up FORGE_TOKEN_ + # where USER_UPPER = tr 'a-z-' 'A-Z_' of the agent's forge_user. + local agent_upper + agent_upper=$(echo "$agent_name" | tr 'a-z-' 'A-Z_') + local token_var="FORGE_TOKEN_${agent_upper}" + local pass_var="FORGE_PASS_${agent_upper}" # Generate token using the user's password (basic auth) local agent_token="" @@ -194,7 +198,7 @@ disinto_hire_an_agent() { if [ -z "$agent_token" ]; then echo " Warning: failed to create API token for '${agent_name}'" >&2 else - # Store token in .env under the role-specific variable name + # Store token in .env under the per-agent variable name if grep -q "^${token_var}=" "$env_file" 2>/dev/null; then # Use sed with alternative delimiter and proper escaping for special chars in token local escaped_token @@ -208,6 +212,23 @@ disinto_hire_an_agent() { export "${token_var}=${agent_token}" fi + # Persist FORGE_PASS_ to .env (#834 Gap 2). + # The container's git credential helper (docker/agents/entrypoint.sh) needs + # both FORGE_TOKEN_* and FORGE_PASS_* to pass HTTPS auth for git push + # (Forgejo 11.x rejects API tokens for git push, #361). + if [ -n "${user_pass:-}" ]; then + local escaped_pass + escaped_pass=$(printf '%s\n' "$user_pass" | sed 's/[&/\]/\\&/g') + if grep -q "^${pass_var}=" "$env_file" 2>/dev/null; then + sed -i "s|^${pass_var}=.*|${pass_var}=${escaped_pass}|" "$env_file" + echo " ${agent_name} password updated (${pass_var})" + else + printf '%s=%s\n' "$pass_var" "$user_pass" >> "$env_file" + echo " ${agent_name} password saved (${pass_var})" + fi + export "${pass_var}=${user_pass}" + fi + # Step 2: Create .profile repo on Forgejo echo "" echo "Step 2: Creating '${agent_name}/.profile' repo (if not exists)..."