Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 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
39 changes: 34 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,51 @@ on:
- "src/**"
- "crates/**"
- "operator/**"
- "scripts/teams-ack-drop-proxy.py"
- "scripts/test-teams-ack-drop-proxy.py"
- ".github/workflows/ci.yml"
- "Cargo.toml"
- "Cargo.lock"
- "Dockerfile*"

env:
CARGO_TERM_COLOR: always

permissions:
contents: read

jobs:
changes:
runs-on: ubuntu-latest
outputs:
core: ${{ steps.filter.outputs.core }}
operator: ${{ steps.filter.outputs.operator }}
teams_ack_proxy: ${{ steps.filter.outputs.teams_ack_proxy }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
persist-credentials: false
- id: filter
env:
BASE: ${{ github.event.pull_request.base.sha }}
HEAD: ${{ github.event.pull_request.head.sha }}
run: |
CHANGED=$(git diff --name-only "$BASE" "$HEAD")
echo "core=$(echo "$CHANGED" | grep -qE '^(src/|crates/|Cargo\.(toml|lock))' && echo true || echo false)" >> "$GITHUB_OUTPUT"
echo "operator=$(echo "$CHANGED" | grep -q '^operator/' && echo true || echo false)" >> "$GITHUB_OUTPUT"
{
echo "core=$(echo "$CHANGED" | grep -qE '^(src/|crates/|Cargo\.(toml|lock)|\.github/workflows/ci\.yml$)' && echo true || echo false)"
echo "operator=$(echo "$CHANGED" | grep -q '^operator/' && echo true || echo false)"
echo "teams_ack_proxy=$(echo "$CHANGED" | grep -qE '^(scripts/(teams-ack-drop-proxy|test-teams-ack-drop-proxy)\.py|\.github/workflows/ci\.yml)$' && echo true || echo false)"
} >> "$GITHUB_OUTPUT"

check:
needs: changes
if: needs.changes.outputs.core == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable (2026-07-13)
with:
components: clippy
Expand Down Expand Up @@ -94,6 +107,20 @@ jobs:
- name: cargo build (unified)
run: cargo build --features unified

teams-ack-proxy:
needs: changes
if: needs.changes.outputs.teams_ack_proxy == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
- name: Test bounded Teams ACK-drop proxy
run: |
set -euo pipefail
python3 -m py_compile scripts/teams-ack-drop-proxy.py scripts/test-teams-ack-drop-proxy.py
python3 -W error::ResourceWarning scripts/test-teams-ack-drop-proxy.py

operator:
needs: changes
if: needs.changes.outputs.operator == 'true'
Expand All @@ -102,7 +129,9 @@ jobs:
run:
working-directory: operator
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable (2026-07-13)
with:
components: clippy
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ gateway/target/
config.toml
*.swp
.DS_Store
__pycache__/
*.py[cod]
.env
.kiro/

Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

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

12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,17 @@ See [docs/wecom.md](docs/wecom.md) for the full setup guide. Requires the standa

</details>

<details>
<summary><strong>Microsoft Teams</strong> (Unified or Custom Gateway)</summary>

Start with the [self-hosted setup guide](docs/msteams-selfhosted.md), or use the
[enterprise Kubernetes guide](docs/msteams-enterprise.md). Before relying on a
specific capability or scope, check the
[live-validation tracker](docs/msteams-live-validation.md). Teams attachment
behavior is documented in [Inbound Attachments](docs/inbound-attachments.md).

</details>

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

```bash
Expand Down Expand Up @@ -168,6 +179,7 @@ kubectl rollout restart deployment/openab-kiro
### 4. Use

In your Discord channel:

```
@YourBot explain this code
```
Expand Down
11 changes: 11 additions & 0 deletions README.zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,16 @@ platforms 使用 `webhook/API`,Feishu/Lark 則使用 `WS/webhook`。

</details>

<details>
<summary><strong>Microsoft Teams</strong>(Unified 或 Custom Gateway)</summary>

請先閱讀 [self-hosted 設定指南](docs/msteams-selfhosted.md);企業 Kubernetes
部署則使用 [enterprise 指南](docs/msteams-enterprise.md)。依賴特定功能或
scope 前,請檢查 [live-validation tracker](docs/msteams-live-validation.md)。
Teams attachment 行為記錄於 [Inbound Attachments](docs/inbound-attachments.md)。

</details>

### 2. 使用 Helm 安裝(Kiro CLI — 預設)

```bash
Expand Down Expand Up @@ -168,6 +178,7 @@ kubectl rollout restart deployment/openab-kiro
### 4. 使用方式

在 Discord 頻道中輸入:

```
@YourBot explain this code
```
Expand Down
57 changes: 54 additions & 3 deletions charts/openab/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ Each agent lives under `agents.<name>`.
| `nameOverride` | Override this agent's generated resource name. | `""` |
| `workingDir` | Working directory and HOME inside the container. | `"/home/agent"` |
| `env` | Inline environment variables passed to the agent process. | `{}` |
| `envFrom` | Additional environment sources from existing Secrets or ConfigMaps. | `[]` |
| `envFrom` | Additional environment sources from existing Secrets or ConfigMaps for the OpenAB process. | `[]` |
| `secretEnv` | Individual Secret keys injected into the OpenAB process. Raw `configToml` must explicitly pass only required agent credentials through `[agent].env` or `inherit_env`; adapter and Gateway secrets must not reach the ACP child. | `[]` |
| `pool.maxSessions` | Maximum concurrent ACP sessions for the agent. | `10` |
| `pool.sessionTtlHours` | Idle session TTL in hours. | `24` |
| `reactions.enabled` | Enable status reactions. | `true` |
Expand All @@ -48,8 +49,13 @@ Each agent lives under `agents.<name>`.
| `stt.baseUrl` | STT API base URL. | `"https://api.groq.com/openai/v1"` |
| `gateway.enabled` | Enable the gateway config block for webhook-based platforms. | `false` |
| `gateway.deploy` | Deploy the gateway Deployment and Service. | `true` |
| `cron.usercronEnabled` | Enable user-provided cron configuration. | `false` |
| `cronjobs` | Config-driven scheduled messages for an agent. | `[]` |
| `gateway.teams.reactionsEnabled` | Opt in to Microsoft public-preview Bot Connector reactions. | `false` |
| `gateway.teams.inboundAttachments` | Enable metadata-first Teams image/text ingress on both Core and Gateway. | `false` |
| `gateway.teams.conversationRegistryPath` | Opt in to the Gateway-local persistent Teams conversation registry. Mount the path separately. | `""` |
| `gateway.teams.conversationRegistryMaxEntries` | Persistent Teams registry entry cap. | `1000` |
| `gateway.teams.conversationRegistryTtlSecs` | Active/disabled registry retention window. | `31536000` |
| `configToml` | Raw authoritative `config.toml`, including baseline `[[cron.jobs]]`. Required unless `configUrl` is used. | `""` |
| `configUrl` | External authoritative config URL; mutually exclusive with the rendered ConfigMap path. | `""` |
| `persistence.enabled` | Enable persistent storage for auth and settings. | `true` |
| `persistence.existingClaim` | Reuse an existing PVC instead of creating one. | `""` |
| `agentsMd` | Contents of `AGENTS.md` mounted into the working directory. | `""` |
Expand Down Expand Up @@ -112,6 +118,51 @@ See [`docs/migrate-to-configtoml.md`](../../docs/migrate-to-configtoml.md) for a
[`docs/adr/configurl-over-helm-rendering.md`](../../docs/adr/configurl-over-helm-rendering.md) for when to prefer `configUrl` instead
(platform-agnostic — works identically on Kubernetes, ECS, Zeabur, and AgentCore).

For Teams typed scope, put the policy in that raw TOML rather than under the Gateway transport values:

```toml
[teams]
allowed_teams = []
allowed_channels = [] # both empty = all Team channels; otherwise Team OR channel match
allow_personal = true
allow_group_chats = true
```

Presence of any of these four fields opts into typed L2 policy. In Standalone Gateway mode, the policy still belongs to the OpenAB Core `configToml`; `gateway.teams.*` configures transport credentials and reaction preview on the Gateway container.

`gateway.teams.inboundAttachments=true` is the exception that must stay aligned across processes: the chart emits `TEAMS_INBOUND_ATTACHMENTS=true` into both Core and Gateway. It enables bounded metadata-first image/text materialization only after Core trust admission. When `gateway.deploy=false`, configure the same environment variable on the external Gateway yourself.

`gateway.teams.conversationRegistryPath` is a separate Gateway-only opt-in. The chart does not silently provision or attach a Gateway PVC; use `gateway.extraVolumeMounts` and `gateway.extraVolumes` (prefer an externally managed PVC with `"helm.sh/resource-policy": keep`) so the configured file survives pod replacement. An empty path preserves the previous process-local behavior and emits no registry environment variables.

Teams operator cron belongs only in the raw Core `configToml`; the chart does not create a parallel target selector:

```toml
[[cron.jobs]]
schedule = "0 9 * * 1-5"
platform = "teams"
channel = "<teams-conversation-id>"
teams_tenant_id = "<tenant-id>"
message = "summarize yesterday's merged work"
timezone = "Asia/Taipei"
```

This requires an exact active record in the Gateway registry. `serviceUrl` remains Gateway-local, `thread_id` is invalid for Teams, and agent-writable usercron cannot select the record.

### Discord ID precision warning

Discord IDs must be set with `--set-string`, not `--set`. Otherwise Helm may coerce them into numbers and lose precision.

## Maintaining This Reference

- **Trigger:** any change to a commonly documented key, default, generated
resource, or Teams transport/registry value in `values.yaml` or `templates/`.
- **Action:** update the table or example in this file, then run:

```bash
helm template test charts/openab --set agents.kiro.enabled=false
helm template test charts/openab \
--set-file agents.kiro.configToml=config.toml.example
```

- **Why:** [`values.yaml`](values.yaml) and the templates are authoritative;
this README is a curated operator view and must not silently drift from them.
4 changes: 4 additions & 0 deletions charts/openab/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ spec:
name: {{ include "openab.agentFullname" $d }}
key: gateway-ws-token
{{- end }}
{{- if and ($cfg.gateway).enabled (hasKey (($cfg.gateway).teams) "inboundAttachments") }}
- name: TEAMS_INBOUND_ATTACHMENTS
value: {{ ($cfg.gateway).teams.inboundAttachments | quote }}
{{- end }}
- name: HOME
value: {{ $cfg.workingDir | default "/home/agent" }}
{{- range $k, $v := $cfg.env }}
Expand Down
16 changes: 16 additions & 0 deletions charts/openab/templates/gateway.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,22 @@ spec:
- name: TEAMS_WEBHOOK_PATH
value: {{ ($cfg.gateway).teams.webhookPath | quote }}
{{- end }}
{{- if hasKey (($cfg.gateway).teams) "reactionsEnabled" }}
- name: TEAMS_REACTIONS_ENABLED
value: {{ ($cfg.gateway).teams.reactionsEnabled | quote }}
{{- end }}
{{- if hasKey (($cfg.gateway).teams) "inboundAttachments" }}
- name: TEAMS_INBOUND_ATTACHMENTS
value: {{ ($cfg.gateway).teams.inboundAttachments | quote }}
{{- end }}
{{- if ($cfg.gateway).teams.conversationRegistryPath }}
- name: TEAMS_CONVERSATION_REGISTRY_PATH
value: {{ ($cfg.gateway).teams.conversationRegistryPath | quote }}
- name: TEAMS_CONVERSATION_REGISTRY_MAX_ENTRIES
value: {{ ($cfg.gateway).teams.conversationRegistryMaxEntries | int64 | quote }}
- name: TEAMS_CONVERSATION_REGISTRY_TTL_SECS
value: {{ ($cfg.gateway).teams.conversationRegistryTtlSecs | int64 | quote }}
{{- end }}
{{- end }}
{{- $hasFeishu := and (($cfg.gateway).feishu).appId (($cfg.gateway).feishu).appSecret }}
{{- if $hasFeishu }}
Expand Down
57 changes: 57 additions & 0 deletions charts/openab/tests/teams_registry_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
suite: Teams persistent conversation registry rendering
templates:
- templates/gateway.yaml

set:
agents.kiro.gateway.enabled: true
agents.kiro.gateway.teams.appId: test-app
agents.kiro.gateway.teams.appSecret: test-secret

tests:
- it: keeps registry disabled and adds no volume by default
asserts:
- notContains:
path: spec.template.spec.containers[0].env
content:
name: TEAMS_CONVERSATION_REGISTRY_PATH
- notExists:
path: spec.template.spec.volumes

- it: renders integer defaults after an explicit path
set:
agents.kiro.gateway.teams.conversationRegistryPath: /var/lib/openab/teams/conversations.json
asserts:
- contains:
path: spec.template.spec.containers[0].env
content:
name: TEAMS_CONVERSATION_REGISTRY_MAX_ENTRIES
value: "1000"
- contains:
path: spec.template.spec.containers[0].env
content:
name: TEAMS_CONVERSATION_REGISTRY_TTL_SECS
value: "31536000"

- it: renders bounded registry settings only after an explicit path
set:
agents.kiro.gateway.teams.conversationRegistryPath: /var/lib/openab/teams/conversations.json
agents.kiro.gateway.teams.conversationRegistryMaxEntries: 123
agents.kiro.gateway.teams.conversationRegistryTtlSecs: 456
asserts:
- contains:
path: spec.template.spec.containers[0].env
content:
name: TEAMS_CONVERSATION_REGISTRY_PATH
value: /var/lib/openab/teams/conversations.json
- contains:
path: spec.template.spec.containers[0].env
content:
name: TEAMS_CONVERSATION_REGISTRY_MAX_ENTRIES
value: "123"
- contains:
path: spec.template.spec.containers[0].env
content:
name: TEAMS_CONVERSATION_REGISTRY_TTL_SECS
value: "456"
- notExists:
path: spec.template.spec.volumes
28 changes: 23 additions & 5 deletions charts/openab/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,10 @@ agents:
# nameOverride: ""
# env: {}
# envFrom: []
# # secretEnv: inject API keys from a Kubernetes Secret without storing them in the ConfigMap.
# # Each entry renders as valueFrom.secretKeyRef in the Deployment and auto-adds the key name
# # to inherit_env in config.toml. ⚠️ Do NOT also list the same key in env — use one or the other.
# # secretEnv: inject values from a Kubernetes Secret into the OpenAB process
# # without storing them in the ConfigMap. Raw configToml is mounted verbatim;
# # pass only required agent credentials through [agent].env or inherit_env.
# # Adapter and Gateway secrets must stay out of the ACP child environment.
# # secretEnv:
# # - name: GEMINI_API_KEY
# # secretName: my-secrets
Expand Down Expand Up @@ -102,6 +103,12 @@ agents:
# # allowed_channels = ["C01234567"]
# # allow_user_messages = "mentions"
# #
# # [teams]
# # allowed_teams = [] # Team IDs; both lists empty = all Team channels
# # allowed_channels = [] # a Team OR channel match admits
# # allow_personal = true
# # allow_group_chats = true
# #
# # [agent]
# # command = "claude-agent-acp"
# # inherit_env = ["ANTHROPIC_API_KEY"]
Expand Down Expand Up @@ -389,7 +396,9 @@ agents:
env: {}
# Load env vars from existing Secrets or ConfigMaps, e.g. GH_TOKEN.
envFrom: []
secretEnv: [] # list of {name, secretName, secretKey} — rendered as valueFrom.secretKeyRef; keys auto-added to inherit_env
# list of {name, secretName, secretKey}; injected into OpenAB only. Raw
# configToml decides explicitly which values, if any, reach the ACP child.
secretEnv: []
pool:
maxSessions: 10
sessionTtlHours: 24
Expand All @@ -416,7 +425,7 @@ agents:
gateway:
enabled: false # set to true + provide url to enable the [gateway] config block
deploy: true # set to false to skip Gateway Deployment/Service (config-only mode)
url: "" # e.g. ws://openab-gateway:8080/ws
url: "" # WebSocket URL, e.g. the in-cluster openab-gateway Service
platform: "telegram" # default platform when gateway is enabled
token: "" # optional shared secret (injected via GATEWAY_WS_TOKEN env var)
botUsername: "" # optional, for @mention gating
Expand Down Expand Up @@ -461,6 +470,15 @@ agents:
openidMetadata: "" # Override for sovereign clouds → TEAMS_OPENID_METADATA
allowedTenants: [] # List of tenant IDs → TEAMS_ALLOWED_TENANTS
webhookPath: "" # Gateway default: /webhook/teams → TEAMS_WEBHOOK_PATH
reactionsEnabled: false # Public-preview Bot Connector reactions → TEAMS_REACTIONS_ENABLED
# Default-off metadata-first image/text ingress. Sets the same env on
# Core and Gateway; no Microsoft URL or token crosses into Core.
inboundAttachments: false # → TEAMS_INBOUND_ATTACHMENTS
# Optional trusted persistent conversation registry. The chart does not create a
# Gateway PVC; mount this path with gateway.extraVolumeMounts/extraVolumes.
conversationRegistryPath: "" # disabled → TEAMS_CONVERSATION_REGISTRY_PATH
conversationRegistryMaxEntries: 1000 # → TEAMS_CONVERSATION_REGISTRY_MAX_ENTRIES
conversationRegistryTtlSecs: 31536000 # → TEAMS_CONVERSATION_REGISTRY_TTL_SECS
# Feishu/Lark adapter config (gateway-side env vars)
# See docs/feishu.md for full setup guide
feishu:
Expand Down
Loading
Loading