Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 10 additions & 13 deletions ansible/playbook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- git
- curl
- jq
- gettext-base
state: present
update_cache: true
- name: Add Docker GPG key
Expand Down Expand Up @@ -251,22 +252,18 @@
when: openrouter_api_key | length > 0
# no_log: true

- name: Copy orchestrator manifest to temporary file
- name: Write .env.k8s on VPS
ansible.builtin.copy:
remote_src: true
src: "{{ project_dir }}/k8s/orchestrator.yaml"
dest: /tmp/orchestrator-rendered.yaml
mode: "0644"

- name: Patch ingress host in temporary manifest
ansible.builtin.replace:
path: /tmp/orchestrator-rendered.yaml
regexp: "bot\\.yourdomain\\.com"
replace: "{{ ingress_host }}"
dest: "{{ project_dir }}/.env.k8s"
content: "ORCHESTRATOR_HOST={{ ingress_host }}\n"
mode: "0600"

- name: Apply orchestrator (Deployment + Service + Ingress)
ansible.builtin.command:
cmd: "k3s kubectl apply -f /tmp/orchestrator-rendered.yaml"
ansible.builtin.shell: |
set -a && . {{ project_dir }}/.env.k8s && set +a
envsubst < {{ project_dir }}/k8s/orchestrator.yaml | k3s kubectl apply -f -
args:
executable: /bin/bash
register: apply_orchestrator
changed_when: "'created' in apply_orchestrator.stdout or 'configured' in apply_orchestrator.stdout"

Expand Down
3 changes: 2 additions & 1 deletion app/agent_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
SourceProviderConfigurationError,
SourceProviderError,
)
from providers.source.models import AGENT_ACTION_NAMES

MAX_ISSUE_BODY_CHARS = 65536

IMPLEMENTED_ACTIONS = {"implement"}
IMPLEMENTED_ACTIONS = AGENT_ACTION_NAMES
AGENT_STATUS_PREFIX = "agent:status:"
AGENT_STATUS_IDLE = "agent:status:idle"
AGENT_STATUS_RUNNING = "agent:status:running"
Expand Down
1 change: 1 addition & 0 deletions k8s/orchestrator.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ORCHESTRATOR_HOST=bot.yourdomain.com
10 changes: 5 additions & 5 deletions k8s/orchestrator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ spec:
secretKeyRef:
name: orchestrator-config
key: JOB_TTL_SECONDS
- name: PROVIDER_LABEL_PREFIX
value: "ai-pr-"
- name: PROVIDER_LABEL_PREFIX
value: "ai-pr-"
- name: ADMIN_TOKEN
valueFrom:
secretKeyRef:
Expand Down Expand Up @@ -94,12 +94,12 @@ metadata:
traefik.ingress.kubernetes.io/router.tls.certresolver: le
spec:
ingressClassName: traefik
# Replace bot.yourdomain.com with your domain (Ansible does this automatically)
# Set ORCHESTRATOR_HOST before applying: envsubst < k8s/orchestrator.yaml | kubectl apply -f -
tls:
- hosts:
- bot.yourdomain.com
- ${ORCHESTRATOR_HOST}
rules:
- host: bot.yourdomain.com
- host: ${ORCHESTRATOR_HOST}
http:
paths:
- path: /webhook/github
Expand Down
12 changes: 11 additions & 1 deletion providers/source/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
from .base import SourceProvider, SourceProviderAPIError, SourceProviderConfigurationError, SourceProviderError
from .factory import get_provider, register_provider
from .models import ActionName, ActionTrigger, Comment, Issue, PullRequest, WebhookEvent, WebhookEventType
from .models import (
AGENT_ACTION_NAMES,
ActionName,
ActionTrigger,
Comment,
Issue,
PullRequest,
WebhookEvent,
WebhookEventType,
)

__all__ = [
"AGENT_ACTION_NAMES",
"ActionName",
"ActionTrigger",
"Comment",
Expand Down
5 changes: 2 additions & 3 deletions providers/source/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import jwt

from .base import SourceProvider, SourceProviderAPIError, SourceProviderConfigurationError
from .models import ActionTrigger, Comment, Issue, PullRequest, WebhookEvent
from .models import AGENT_ACTION_NAMES, ActionTrigger, Comment, Issue, PullRequest, WebhookEvent

logger = logging.getLogger("uvicorn.error")

Expand All @@ -21,7 +21,6 @@
JWT_LEEWAY_SECONDS = 60
JWT_TTL_SECONDS = 600
DEFAULT_INSTALLATION_TOKEN_TTL_SECONDS = 3300
AGENT_ACTIONS = {"spec", "plan", "implement", "review", "continue"}
ACTION_TRIGGER_COMMAND_MAX_CHARS = 64
ACTION_TRIGGER_ALLOWED_PERMISSIONS = {"write", "maintain", "admin"}

Expand Down Expand Up @@ -177,7 +176,7 @@ def get_action_trigger(self, event: WebhookEvent) -> ActionTrigger | None:
if len(parts) < 2 or parts[0] != "/agent":
continue
action = parts[1].lower()
if action not in AGENT_ACTIONS:
if action not in AGENT_ACTION_NAMES:
continue
raw_command = f"/agent {action}"[:ACTION_TRIGGER_COMMAND_MAX_CHARS]
return ActionTrigger(action=action, source="comment", raw_command=raw_command)
Expand Down
4 changes: 3 additions & 1 deletion providers/source/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from datetime import datetime
from typing import Literal
from typing import Literal, get_args

from pydantic import BaseModel, Field

Expand Down Expand Up @@ -44,6 +44,8 @@ class PullRequest(BaseModel):

ActionName = Literal["spec", "plan", "implement", "review", "continue"]

AGENT_ACTION_NAMES: frozenset[str] = frozenset(get_args(ActionName))


class ActionTrigger(BaseModel):
action: ActionName
Expand Down
4 changes: 4 additions & 0 deletions tests/app/test_webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,10 @@ async def test_normal_issue_comment_does_not_trigger_job(monkeypatch):

@pytest.mark.asyncio
async def test_known_but_unimplemented_action_sets_awaiting_human(monkeypatch):
import app.agent_orchestrator as agent_orchestrator_module

monkeypatch.setattr(agent_orchestrator_module, "IMPLEMENTED_ACTIONS", frozenset({"implement"}))

payload = load_payload("issue_labeled.json")
event = issue_comment_event(payload, body="/agent plan")

Expand Down
Loading