Skip to content
Open
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
6e3015c
feat: add Slack adapter with multi-platform ChatAdapter architecture
tonylee-shopback Apr 10, 2026
bd61069
refactor: extract shared media module, centralize sender context
tonylee-shopback Apr 12, 2026
67d76ab
chore: remove unused methods (is_thread, pool, is_image_mime)
tonylee-shopback Apr 12, 2026
ecb983b
fix: review findings — regex LazyLock, user name resolution, thread f…
tonylee-shopback Apr 12, 2026
9c1c35a
feat(helm): add Slack adapter support to Helm chart
tonylee-shopback Apr 12, 2026
a76f7a6
docs: add Slack adapter documentation
tonylee-shopback Apr 13, 2026
0e04df0
fix(helm): align discord/slack enabled conditions across all templates
dogzzdogzz Apr 13, 2026
1560206
feat(helm): add global labels/annotations and per-agent podLabels/pod…
dogzzdogzz Apr 13, 2026
74387eb
fix(slack): allow file_share subtype in thread follow-ups
dogzzdogzz Apr 13, 2026
7811e76
fix: address PR review feedback
dogzzdogzz Apr 14, 2026
4a159dc
fix: resolve clippy errors (too_many_arguments, unnecessary_map_or)
dogzzdogzz Apr 14, 2026
37b6275
feat(slack): convert Markdown to Slack mrkdwn format
dogzzdogzz Apr 14, 2026
a7c5f50
feat(helm): support automountServiceAccountToken in deployment
dogzzdogzz Apr 14, 2026
6ec589c
Merge origin/main into feat/slack-adapter
dogzzdogzz Apr 14, 2026
cd9ea56
Merge origin/main into feat/slack-adapter
dogzzdogzz Apr 14, 2026
42bb000
Merge origin/main (v0.7.5) into feat/slack-adapter
dogzzdogzz Apr 15, 2026
b758fd3
Merge origin/main: port voice message 🎤 reaction (STT disabled)
dogzzdogzz Apr 15, 2026
eeae9de
Merge origin/main: add OpenCode agent backend support
dogzzdogzz Apr 15, 2026
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
33 changes: 18 additions & 15 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ serenity = { version = "0.12", default-features = false, features = ["client", "
uuid = { version = "1", features = ["v4"] }
regex = "1"
anyhow = "1"
async-trait = "0.1"
futures-util = "0.3"
rand = "0.8"
clap = { version = "4", features = ["derive"] }
rpassword = "7"
Expand All @@ -22,3 +24,4 @@ base64 = "0.22"
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "gif", "webp"] }
unicode-width = "0.2"
libc = "0.2"
tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] }
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add lru crate for bounded user cache:

Suggested change
tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] }
libc = "0.2"
lru = "0.12"
tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add lru crate for bounded user cache:

Suggested change
tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] }
libc = "0.2"
lru = "0.12"
tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] }

52 changes: 43 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# OpenAB — Open Agent Broker

A lightweight, secure, cloud-native ACP harness that bridges Discord and any [Agent Client Protocol](https://github.com/anthropics/agent-protocol)-compatible coding CLI (Kiro CLI, Claude Code, Codex, Gemini, OpenCode, Copilot CLI, etc.) over stdio JSON-RPC — delivering the next-generation development experience.
A lightweight, secure, cloud-native ACP harness that bridges **Discord, Slack**, and any [Agent Client Protocol](https://github.com/anthropics/agent-protocol)-compatible coding CLI (Kiro CLI, Claude Code, Codex, Gemini, OpenCode, Copilot CLI, etc.) over stdio JSON-RPC — delivering the next-generation development experience.

🪼 **Join our community!** Come say hi on Discord — we'd love to have you: **[🪼 OpenAB — Official](https://discord.gg/YNksK9M6)** 🎉

```
┌──────────────┐ Gateway WS ┌──────────────┐ ACP stdio ┌──────────────┐
│ Discord │◄─────────────►│ openab │──────────────►│ coding CLI │
│ User │ │ (Rust) │◄── JSON-RPC ──│ (acp mode) │
└──────────────┘ └──────────────┘ └──────────────┘
│ Discord │◄─────────────►│ │──────────────►│ coding CLI │
│ User │ │ openab │◄── JSON-RPC ──│ (acp mode) │
├──────────────┤ Socket Mode │ (Rust) │ └──────────────┘
│ Slack │◄─────────────►│ │
│ User │ └──────────────┘
└──────────────┘
```

## Demo
Expand All @@ -17,6 +20,7 @@ A lightweight, secure, cloud-native ACP harness that bridges Discord and any [Ag

## Features

- **Multi-platform** — supports Discord and Slack, run one or both simultaneously
- **Pluggable agent backend** — swap between Kiro CLI, Claude Code, Codex, Gemini, OpenCode, Copilot CLI via config
- **@mention trigger** — mention the bot in an allowed channel to start a conversation
- **Thread-based multi-turn** — auto-creates threads; no @mention needed for follow-ups
Expand All @@ -29,10 +33,22 @@ A lightweight, secure, cloud-native ACP harness that bridges Discord and any [Ag

## Quick Start

### 1. Create a Discord Bot
### 1. Create a Bot

<details>
<summary><strong>Discord</strong></summary>

See [docs/discord-bot-howto.md](docs/discord-bot-howto.md) for a detailed step-by-step guide.

</details>

<details>
<summary><strong>Slack</strong></summary>

See [docs/slack-bot-howto.md](docs/slack-bot-howto.md) for a detailed step-by-step guide.

</details>

### 2. Install with Helm (Kiro CLI — default)

```bash
Expand All @@ -42,6 +58,13 @@ helm repo update
helm install openab openab/openab \
--set agents.kiro.discord.botToken="$DISCORD_BOT_TOKEN" \
--set-string 'agents.kiro.discord.allowedChannels[0]=YOUR_CHANNEL_ID'

# Slack
helm install openab openab/openab \
--set agents.kiro.slack.enabled=true \
--set agents.kiro.slack.botToken="$SLACK_BOT_TOKEN" \
--set agents.kiro.slack.appToken="$SLACK_APP_TOKEN" \
--set-string 'agents.kiro.slack.allowedChannels[0]=C0123456789'
```

### 3. Authenticate (first time only)
Expand All @@ -60,6 +83,8 @@ In your Discord channel:

The bot creates a thread. After that, just type in the thread — no @mention needed.

**Slack:** `@YourBot explain this code` in a channel — same thread-based workflow as Discord.

## Other Agents

| Agent | CLI | ACP Adapter | Guide |
Expand Down Expand Up @@ -91,6 +116,12 @@ bot_token = "${DISCORD_BOT_TOKEN}" # supports env var expansion
allowed_channels = ["123456789"] # channel ID allowlist
# allowed_users = ["987654321"] # user ID allowlist (empty = all users)

[slack]
bot_token = "${SLACK_BOT_TOKEN}" # Bot User OAuth Token (xoxb-...)
app_token = "${SLACK_APP_TOKEN}" # App-Level Token (xapp-...) for Socket Mode
allowed_channels = ["C0123456789"] # channel ID allowlist (empty = allow all)
# allowed_users = ["U0123456789"] # user ID allowlist (empty = allow all)

[agent]
command = "kiro-cli" # CLI command
args = ["acp", "--trust-all-tools"] # ACP mode args
Expand Down Expand Up @@ -179,15 +210,18 @@ kubectl apply -f k8s/deployment.yaml
├── config.toml.example # example config with all agent backends
├── k8s/ # Kubernetes manifests
└── src/
├── main.rs # entrypoint: tokio + serenity + cleanup + shutdown
├── main.rs # entrypoint: multi-adapter startup, cleanup, shutdown
├── adapter.rs # ChatAdapter trait, AdapterRouter (platform-agnostic)
├── config.rs # TOML config + ${ENV_VAR} expansion
├── discord.rs # Discord bot: mention, threads, edit-streaming
├── format.rs # message splitting (2000 char limit)
├── discord.rs # DiscordAdapter: serenity EventHandler + ChatAdapter impl
├── slack.rs # SlackAdapter: Socket Mode + ChatAdapter impl
├── media.rs # shared image resize/compress + STT download
├── format.rs # message splitting, thread name shortening
├── reactions.rs # status reaction controller (debounce, stall detection)
└── acp/
├── protocol.rs # JSON-RPC types + ACP event classification
├── connection.rs # spawn CLI, stdio JSON-RPC communication
└── pool.rs # thread_id → AcpConnection map
└── pool.rs # session key → AcpConnection map
```

## Inspired By
Expand Down
15 changes: 12 additions & 3 deletions charts/openab/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
openab {{ .Chart.AppVersion }} has been installed!

⚠️ Discord channel IDs must be set with --set-string (not --set) to avoid float64 precision loss.
⚠️ Channel/user IDs must be set with --set-string (not --set) to avoid float64 precision loss.

Agents deployed:
{{- range $name, $cfg := .Values.agents }}
{{- if ne (include "openab.agentEnabled" $cfg) "false" }}
• {{ $name }} ({{ $cfg.command }})
{{- if not $cfg.discord.botToken }}
{{- if not (or (and ($cfg.discord).enabled ($cfg.discord).botToken) (and ($cfg.slack).enabled ($cfg.slack).botToken)) }}
⚠️ No bot token provided. Create the secret manually:
kubectl create secret generic {{ include "openab.agentFullname" (dict "ctx" $ "agent" $name) }} \
--from-literal=discord-bot-token="YOUR_TOKEN"
--from-literal=discord-bot-token="YOUR_DISCORD_TOKEN"
{{- end }}

{{- if and ($cfg.discord).enabled ($cfg.discord).botToken }}
Discord: ✅ configured
{{- end }}
{{- if and ($cfg.slack).enabled ($cfg.slack).botToken }}
Slack: ✅ configured (Socket Mode)
Ensure your Slack app has these bot events: app_mention, message.channels, message.groups
Required scopes: app_mentions:read, chat:write, channels:history, groups:history, channels:read, groups:read, reactions:write, files:read
{{- end }}

{{- if eq $cfg.command "kiro-cli" }}
Expand Down
9 changes: 9 additions & 0 deletions charts/openab/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ app.kubernetes.io/component: {{ .agent }}
app.kubernetes.io/version: {{ .ctx.Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .ctx.Release.Service }}
{{- with .ctx.Values.labels }}
{{ toYaml . }}
{{- end }}
{{- end }}

{{- define "openab.annotations" -}}
{{- with .ctx.Values.annotations }}
{{ toYaml . }}
{{- end }}
{{- end }}

{{- define "openab.selectorLabels" -}}
Expand Down
24 changes: 24 additions & 0 deletions charts/openab/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ metadata:
name: {{ include "openab.agentFullname" $d }}
labels:
{{- include "openab.labels" $d | nindent 4 }}
{{- with (include "openab.annotations" $d) }}
annotations:
{{- . | nindent 4 }}
{{- end }}
data:
config.toml: |
{{- if and ($cfg.discord).enabled ($cfg.discord).botToken }}
[discord]
bot_token = "${DISCORD_BOT_TOKEN}"
{{- range $cfg.discord.allowedChannels }}
Expand Down Expand Up @@ -41,6 +46,25 @@ data:
{{- if $cfg.discord.trustedBotIds }}
trusted_bot_ids = {{ $cfg.discord.trustedBotIds | toJson }}
{{- end }}
{{- end }}

{{- if and ($cfg.slack).enabled }}
[slack]
bot_token = "${SLACK_BOT_TOKEN}"
app_token = "${SLACK_APP_TOKEN}"
{{- range ($cfg.slack).allowedChannels }}
{{- if regexMatch "e\\+|E\\+" (toString .) }}
{{- fail (printf "slack.allowedChannels contains a mangled ID: %s — use --set-string instead of --set for channel IDs" (toString .)) }}
{{- end }}
{{- end }}
allowed_channels = {{ ($cfg.slack).allowedChannels | default list | toJson }}
{{- range ($cfg.slack).allowedUsers }}
{{- if regexMatch "e\\+|E\\+" (toString .) }}
{{- fail (printf "slack.allowedUsers contains a mangled ID: %s — use --set-string instead of --set for user IDs" (toString .)) }}
{{- end }}
{{- end }}
allowed_users = {{ ($cfg.slack).allowedUsers | default list | toJson }}
{{- end }}

[agent]
command = "{{ $cfg.command }}"
Expand Down
29 changes: 28 additions & 1 deletion charts/openab/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ metadata:
name: {{ include "openab.agentFullname" $d }}
labels:
{{- include "openab.labels" $d | nindent 4 }}
{{- with (include "openab.annotations" $d) }}
annotations:
{{- . | nindent 4 }}
{{- end }}
spec:
# Hardcoded for PVC-backed agents: RWO volumes can't be shared across pods,
# so rolling updates and multiple replicas are not supported.
Expand All @@ -22,9 +26,18 @@ spec:
metadata:
annotations:
checksum/config: {{ $cfg | toJson | sha256sum }}
{{- with $cfg.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "openab.selectorLabels" $d | nindent 8 }}
{{- with $cfg.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- if hasKey $.Values "automountServiceAccountToken" }}
automountServiceAccountToken: {{ $.Values.automountServiceAccountToken }}
{{- end }}
{{- with $.Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
Expand All @@ -38,13 +51,27 @@ spec:
{{- toYaml . | nindent 12 }}
{{- end }}
env:
{{- if $cfg.discord.botToken }}
{{- if and ($cfg.discord).enabled ($cfg.discord).botToken }}
- name: DISCORD_BOT_TOKEN
valueFrom:
secretKeyRef:
name: {{ include "openab.agentFullname" $d }}
key: discord-bot-token
{{- end }}
{{- if and ($cfg.slack).enabled ($cfg.slack).botToken }}
- name: SLACK_BOT_TOKEN
valueFrom:
secretKeyRef:
name: {{ include "openab.agentFullname" $d }}
key: slack-bot-token
{{- end }}
{{- if and ($cfg.slack).enabled ($cfg.slack).appToken }}
- name: SLACK_APP_TOKEN
valueFrom:
secretKeyRef:
name: {{ include "openab.agentFullname" $d }}
key: slack-app-token
{{- end }}
{{- if and ($cfg.stt).enabled ($cfg.stt).apiKey }}
- name: STT_API_KEY
valueFrom:
Expand Down
4 changes: 4 additions & 0 deletions charts/openab/templates/pvc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ metadata:
name: {{ include "openab.agentFullname" $d }}
labels:
{{- include "openab.labels" $d | nindent 4 }}
{{- with (include "openab.annotations" $d) }}
annotations:
{{- . | nindent 4 }}
{{- end }}
spec:
accessModes:
- ReadWriteOnce
Expand Down
Loading
Loading