Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
13 changes: 8 additions & 5 deletions .github/workflows/deploy-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,15 @@ jobs:
run: |
bin/kelos install --version main --image-pull-policy Always \
--spawner-resource-requests cpu=100m,memory=128Mi \
--ghproxy-resource-requests cpu=10m,memory=64Mi \
--token-refresher-resource-requests cpu=50m,memory=64Mi \
--controller-resource-requests cpu=10m,memory=64Mi \
--controller-resource-limits cpu=500m,memory=128Mi
kubectl rollout restart deployment/kelos-controller-manager -n kelos-system
kubectl rollout status deployment/kelos-controller-manager -n kelos-system --timeout=120s
kubectl rollout restart deployment/ghproxy -n kelos-system
kubectl rollout status deployment/ghproxy -n kelos-system --timeout=120s
kubectl rollout restart deployment -l kelos.dev/component=ghproxy -n "${KELOS_NAMESPACE}"
kubectl rollout restart deployment -l kelos.dev/component=spawner -n "${KELOS_NAMESPACE}"
kubectl rollout status deployment -l kelos.dev/component=ghproxy -n "${KELOS_NAMESPACE}" --timeout=120s
kubectl rollout status deployment -l kelos.dev/component=spawner -n "${KELOS_NAMESPACE}" --timeout=120s

- name: Apply PodMonitoring
Expand Down Expand Up @@ -107,13 +108,15 @@ jobs:
kind: PodMonitoring
metadata:
name: ghproxy
namespace: kelos-system
namespace: ${KELOS_NAMESPACE}
labels:
app.kubernetes.io/name: ghproxy
kelos.dev/name: kelos
kelos.dev/component: ghproxy
spec:
selector:
matchLabels:
app.kubernetes.io/name: ghproxy
kelos.dev/name: kelos
kelos.dev/component: ghproxy
endpoints:
- port: metrics
interval: 30s
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/reusable-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ jobs:
with:
ref: ${{ inputs.checkout-ref }}
persist-credentials: ${{ inputs.persist-credentials }}
fetch-depth: 2

- name: Validate checked-out commit matches approved SHA
if: inputs.expected-sha != ''
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Image configuration
REGISTRY ?= ghcr.io/kelos-dev
VERSION ?= latest
IMAGE_DIRS ?= cmd/kelos-controller cmd/kelos-spawner cmd/kelos-token-refresher cmd/ghproxy claude-code codex gemini opencode cursor
IMAGE_DIRS ?= cmd/kelos-controller cmd/kelos-spawner cmd/kelos-token-refresher cmd/kelos-webhook-server cmd/ghproxy claude-code codex gemini opencode cursor

# Version injection for the kelos CLI – only set ldflags when an explicit
# version is given so that dev builds fall through to runtime/debug info.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ spec:
priorityLabels:
- priority/critical-urgent
- priority/important-soon
pollInterval: 1m
maxConcurrency: 3
taskTemplate:
model: opus
Expand All @@ -101,7 +102,6 @@ spec:
- update an existing PR to fix the issue
- comment on the issue or the PR if you cannot fix it
...
pollInterval: 1m
```

The key pattern is `excludeLabels: [kelos/needs-input]` — this creates a feedback loop where the agent works autonomously until it needs human input, then pauses. Removing the label re-queues the issue on the next poll.
Expand Down Expand Up @@ -343,6 +343,7 @@ spec:
githubIssues:
labels: [bug]
state: open
pollInterval: 5m
taskTemplate:
type: claude-code
workspaceRef:
Expand All @@ -352,7 +353,6 @@ spec:
secretRef:
name: claude-oauth-token
promptTemplate: "Fix: {{.Title}}\n{{.Body}}"
pollInterval: 5m
```

```bash
Expand Down
67 changes: 66 additions & 1 deletion api/v1alpha1/taskspawner_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ type When struct {
// Jira discovers issues from a Jira project.
// +optional
Jira *Jira `json:"jira,omitempty"`

// GitHubWebhook triggers task spawning on GitHub webhook events.
// +optional
GitHubWebhook *GitHubWebhook `json:"githubWebhook,omitempty"`
}

// Cron triggers task spawning on a cron schedule.
Expand Down Expand Up @@ -295,6 +299,65 @@ type Jira struct {
PollInterval string `json:"pollInterval,omitempty"`
}

// GitHubWebhook configures webhook-driven task spawning from GitHub events.
type GitHubWebhook struct {
// Events is the list of GitHub event types to listen for.
// e.g., "issue_comment", "pull_request_review", "push", "issues"
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinItems=1
Events []string `json:"events"`

// Repository restricts webhooks to a specific repository (owner/repo format).
// If empty, webhooks from any repository are accepted.
// +optional
Repository string `json:"repository,omitempty"`

// Filters refine which events trigger tasks. If multiple filters match
// the same event type, any match triggers a task (OR semantics).
// If empty, all events in the Events list trigger tasks.
// +optional
Filters []GitHubWebhookFilter `json:"filters,omitempty"`
}

// GitHubWebhookFilter defines filtering criteria for GitHub webhook events.
type GitHubWebhookFilter struct {
// Event is the GitHub event type this filter applies to.
// +kubebuilder:validation:Required
Event string `json:"event"`

// Action filters by webhook action (e.g., "created", "opened", "submitted").
// +optional
Action string `json:"action,omitempty"`

// BodyContains filters by substring match on the comment/review body.
// +optional
BodyContains string `json:"bodyContains,omitempty"`

// Labels requires the issue/PR to have all of these labels.
// +optional
Labels []string `json:"labels,omitempty"`

// ExcludeLabels excludes issues/PRs with any of these labels.
// +optional
ExcludeLabels []string `json:"excludeLabels,omitempty"`

// State filters by issue/PR state ("open", "closed").
// +optional
State string `json:"state,omitempty"`

// Branch filters push events by branch name (exact match or glob).
// +optional
Branch string `json:"branch,omitempty"`

// Draft filters PRs by draft status. nil = don't filter.
// +optional
Draft *bool `json:"draft,omitempty"`

// Author filters by the event sender's username.
// +optional
Author string `json:"author,omitempty"`
}

// TaskTemplateMetadata holds optional labels and annotations for spawned Tasks.
type TaskTemplateMetadata struct {
// Labels are merged into the spawned Task's labels. Values support Go
Expand Down Expand Up @@ -355,6 +418,7 @@ type TaskTemplate struct {
// Available variables (all sources): {{.ID}}, {{.Title}}, {{.Kind}}
// GitHub issue/Jira sources: {{.Number}}, {{.Body}}, {{.URL}}, {{.Labels}}, {{.Comments}}
// GitHub pull request sources additionally expose: {{.Branch}}, {{.ReviewState}}, {{.ReviewComments}}
// GitHub webhook sources: {{.Event}}, {{.Action}}, {{.Sender}}, {{.Ref}}, {{.Payload}} (full payload access)
// Cron sources: {{.Time}}, {{.Schedule}}
// +optional
Branch string `json:"branch,omitempty"`
Expand All @@ -363,6 +427,7 @@ type TaskTemplate struct {
// Available variables (all sources): {{.ID}}, {{.Title}}, {{.Kind}}
// GitHub issue/Jira sources: {{.Number}}, {{.Body}}, {{.URL}}, {{.Labels}}, {{.Comments}}
// GitHub pull request sources additionally expose: {{.Branch}}, {{.ReviewState}}, {{.ReviewComments}}
// GitHub webhook sources: {{.Event}}, {{.Action}}, {{.Sender}}, {{.Ref}}, {{.Payload}} (full payload access)
// Cron sources: {{.Time}}, {{.Schedule}}
// +optional
PromptTemplate string `json:"promptTemplate,omitempty"`
Expand Down Expand Up @@ -396,7 +461,7 @@ type TaskTemplate struct {
}

// TaskSpawnerSpec defines the desired state of TaskSpawner.
// +kubebuilder:validation:XValidation:rule="!(has(self.when.githubIssues) || has(self.when.githubPullRequests)) || has(self.taskTemplate.workspaceRef)",message="taskTemplate.workspaceRef is required when using githubIssues or githubPullRequests source"
// +kubebuilder:validation:XValidation:rule="!(has(self.when.githubIssues) || has(self.when.githubPullRequests) || has(self.when.githubWebhook)) || has(self.taskTemplate.workspaceRef)",message="taskTemplate.workspaceRef is required when using githubIssues, githubPullRequests, or githubWebhook source"
type TaskSpawnerSpec struct {
// When defines the conditions that trigger task spawning.
// +kubebuilder:validation:Required
Expand Down
62 changes: 62 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion claude-code/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ RUN ARCH=$(dpkg --print-architecture) \

ENV PATH="/usr/local/go/bin:${PATH}"

ARG CLAUDE_CODE_VERSION=2.1.87
ARG CLAUDE_CODE_VERSION=2.1.88
RUN npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}

COPY claude-code/kelos_entrypoint.sh /kelos_entrypoint.sh
Expand Down
Loading
Loading