Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
5c90c70
feat(channels): add GitLab polling channel adapter
OrbitZore Jul 27, 2026
040f713
fix(channels/gitlab): persist cursor after each successful todo
OrbitZore Jul 27, 2026
f99b654
fix(channels/gitlab): persist cursor on every advancement including s…
OrbitZore Jul 27, 2026
b2d410e
fix(channels/gitlab): address review critical issues
OrbitZore Jul 28, 2026
a5db236
fix(channels/gitlab): address review suggestions
OrbitZore Jul 28, 2026
5927ecd
refactor(channels/gitlab): simplify to todo.body dispatch, add descri…
OrbitZore Jul 28, 2026
5ee59bd
docs(channels): add GitLab adapter documentation
OrbitZore Jul 28, 2026
728930d
fix(channels/gitlab): use correct Issues.show(issueIid, { projectId }…
OrbitZore Jul 28, 2026
4a6046b
Merge remote-tracking branch 'upstream/main' into feat/gitlab-channel…
OrbitZore Jul 28, 2026
a16a8c4
chore: regenerate NOTICES.txt for new gitlab channel dependencies
OrbitZore Jul 28, 2026
ab2b228
fix(channels/gitlab): address review suggestions
OrbitZore Jul 28, 2026
fbc1e50
test(channels/gitlab): add mention tests, directly_addressed coverage…
OrbitZore Jul 28, 2026
9568588
fix(channels/gitlab): address review round 4
OrbitZore Jul 28, 2026
7cef052
fix(channels/gitlab): always force isMentioned=true, remove regex re-…
OrbitZore Jul 28, 2026
049a28d
fix(channels/gitlab): propagate fetchDescription errors for descripti…
OrbitZore Jul 28, 2026
200ad9e
perf(channels/gitlab): clean up stale todos, skip unnecessary fetchDe…
OrbitZore Jul 28, 2026
fb7bf93
docs(channels/gitlab): clarify requireMention is bypassed, template i…
OrbitZore Jul 28, 2026
16aac38
Apply suggestions from code review
OrbitZore Jul 28, 2026
d70f6dd
fix(channels/gitlab): use todo ID cursor instead of timestamp to elim…
OrbitZore Jul 28, 2026
7f35c63
fix(channels/gitlab): harden first-poll drain, add ordering tests, fi…
OrbitZore Jul 28, 2026
9bd4463
fix(channels/gitlab): include dot in mention lookahead for GitLab use…
OrbitZore Jul 28, 2026
ba66ba7
docs(channels/gitlab): align docs with ID cursor and drain semantics
OrbitZore Jul 28, 2026
4545543
Apply suggestions from code review
OrbitZore Jul 29, 2026
009c15c
Merge remote-tracking branch 'upstream/main' into feat/gitlab-channel…
OrbitZore Jul 29, 2026
843590e
fix(channels/gitlab): align package version and channel-base dependen…
OrbitZore Jul 29, 2026
310e633
Merge branch 'feat/gitlab-channel-adapter' of github.com:OrbitZore/qw…
OrbitZore Jul 29, 2026
aa89a67
fix(channels/gitlab): regenerate lockfile to match package.json versions
OrbitZore Jul 29, 2026
409d6a8
test(channels/gitlab): add regression tests for first-poll drain hard…
OrbitZore Jul 29, 2026
96b77a4
docs(channels/gitlab): clarify groupPolicy must be "open" and add run…
OrbitZore Jul 29, 2026
bebd3ff
fix(channels/gitlab): correct xcase integrity hash in lockfile
OrbitZore Jul 29, 2026
6b23237
Merge branch 'main' into feat/gitlab-channel-adapter
wenshao Jul 29, 2026
d32549e
fix(channels/gitlab): correct requester-utils integrity hash in lockfile
OrbitZore Jul 29, 2026
b48dbe5
fix(channels/gitlab): allow groupPolicy "allowlist" in warning and docs
OrbitZore Jul 29, 2026
a586788
Merge branch 'main' into feat/gitlab-channel-adapter
wenshao Jul 29, 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
18 changes: 10 additions & 8 deletions docs/developers/daemon/15-channel-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,14 @@ Handles common cross-cutting concerns: sender gating (allowlist / denylist), gro

### Per-channel adapters

| Adapter | File | Transport | Notes |
| --------------- | --------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| DingTalk | `packages/channels/dingtalk/src/DingtalkAdapter.ts` | DingTalk Stream SDK WebSocket | Sends via `sessionWebhook` POST; media images downloaded via DT API, base64 in envelope. |
| WeChat (Weixin) | `packages/channels/weixin/src/WeixinAdapter.ts` | iLink Bot HTTP long-poll | Sends via proprietary `sendText` / `sendImage` API; typing indicators. |
| Telegram | `packages/channels/telegram/src/TelegramAdapter.ts` | Telegram Bot API long-poll (grammy) | Sends HTML chunks via `sendMessage`. |
| Feishu | `packages/channels/feishu/src/FeishuAdapter.ts` | Feishu/Lark Stream WebSocket (default) or HTTP webhook | Sends via Lark SDK as interactive cards; webhook mode requires `encryptKey` for HMAC signature verification. |
| GitHub | `packages/channels/github/src/GithubAdapter.ts` | GitHub Notifications API polling (`@octokit/rest`) | Extends `PollingChannelBase`; cursor-based comment window dedup; posts comments via Issues API. |
| Adapter | File | Transport | Notes |
| --------------- | --------------------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| DingTalk | `packages/channels/dingtalk/src/DingtalkAdapter.ts` | DingTalk Stream SDK WebSocket | Sends via `sessionWebhook` POST; media images downloaded via DT API, base64 in envelope. |
| WeChat (Weixin) | `packages/channels/weixin/src/WeixinAdapter.ts` | iLink Bot HTTP long-poll | Sends via proprietary `sendText` / `sendImage` API; typing indicators. |
| Telegram | `packages/channels/telegram/src/TelegramAdapter.ts` | Telegram Bot API long-poll (grammy) | Sends HTML chunks via `sendMessage`. |
| Feishu | `packages/channels/feishu/src/FeishuAdapter.ts` | Feishu/Lark Stream WebSocket (default) or HTTP webhook | Sends via Lark SDK as interactive cards; webhook mode requires `encryptKey` for HMAC signature verification. |
| GitHub | `packages/channels/github/src/GithubAdapter.ts` | GitHub Notifications API polling (`@octokit/rest`) | Extends `PollingChannelBase`; cursor-based comment window dedup; posts comments via Issues API. |
| GitLab | `packages/channels/gitlab/src/GitlabAdapter.ts` | GitLab Todos API polling (`@gitbeaker/rest`) | Extends `PollingChannelBase`; dispatches `todo.body` directly; `action_prompt_template` config drives event filtering and metadata rendering. |

Each adapter implements:

Expand All @@ -98,6 +99,7 @@ Each adapter implements:
| **Telegram** | Bot API long-poll | `from.id` (+ optional `chat.id` for groups) | Inline keyboard buttons | Same |
| **Feishu** | WebSocket stream / HTTP webhook | `sender.open_id` (+ optional `chat_id` for groups) | Interactive card buttons | Same |
| **GitHub** | Notifications API polling | Numeric `user.id` (immutable; login resolved at connect) | Error comment + re-mention | `senderPolicy: 'allowlist' \| 'open'` |
| **GitLab** | Todos API polling | `author.username` (lowercased) | Log + re-mention | `senderPolicy: 'allowlist' \| 'open'` |

> **Note:** The "Permission UX" column describes each platform's native affordance, but none is wired up yet — `AcpBridge.requestPermission` currently auto-approves every request (`packages/channels/base/src/AcpBridge.ts`), and `ChannelConfig.approvalMode` is declared but not yet read. Interactive approval is planned (Phase 5).

Expand Down Expand Up @@ -186,7 +188,7 @@ Adapter `connect()` failures are reported separately from worker lifecycle error

- `packages/channels/base/` — `ChannelBase`, `PollingChannelBase`, `DaemonChannelBridge`, `types.ts` (`ChannelConfig`, `Envelope`, `SessionScope`, `ChannelPlugin`).
- `packages/sdk-typescript/src/daemon/` — `DaemonSessionClient` and friends.
- Per-channel SDKs: `@dingtalk/stream` (DingTalk), proprietary iLink Bot HTTP (Weixin), `grammy` (Telegram), `@octokit/rest` (GitHub polling).
- Per-channel SDKs: `@dingtalk/stream` (DingTalk), proprietary iLink Bot HTTP (Weixin), `grammy` (Telegram), `@octokit/rest` (GitHub polling), `@gitbeaker/rest` (GitLab polling).

## Configuration

Expand Down
1 change: 1 addition & 0 deletions docs/users/features/channels/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export default {
feishu: 'Feishu',
qqbot: 'QQ Bot',
github: 'GitHub',
gitlab: 'GitLab',
plugins: 'Plugins',
};
165 changes: 165 additions & 0 deletions docs/users/features/channels/gitlab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# GitLab

This guide covers setting up a Qwen Code channel that monitors GitLab todos and responds to mentions on issues and merge requests.

## Prerequisites

- A GitLab account (or a dedicated bot account)
- A GitLab Personal Access Token with `read_api` and `api` scopes

## Creating a Token

1. Go to **Preferences → Access Tokens**
2. Create a token with these scopes:
- **read_api** — read todos and project data
- **api** — post notes (comments) on issues/MRs
3. Save the token securely as an environment variable

## Configuration

Add the channel to `~/.qwen/settings.json`:

```json
{
"channels": {
"my-gitlab": {
"type": "gitlab",
"token": "$GITLAB_TOKEN",
"pollInterval": 60000,
"senderPolicy": "open",
"sessionScope": "chat_thread",
"cwd": "/path/to/your/project",
"groupPolicy": "open",
"action_prompt_template": {
"mentioned": "Project: %project% | URL: %project_url% | Author: %author% | Type: %target_type% | IID: %iid% | Title: %title% | Description: %description% | TodoID: %todo_id%"
}
}
}
}
```

Set the token as an environment variable:

```bash
export GITLAB_TOKEN="glpat-your_token_here"
```

### Self-hosted GitLab

For self-hosted instances, set `baseUrl`:

```json
{
"baseUrl": "https://gitlab.example.com"
}
```

## Configuration Options

| Option | Default | Description |
| ------------------------ | ------------------------- | ---------------------------------------------- |
| `token` | (required) | PAT with `read_api` + `api` scopes |
| `pollInterval` | `60000` | Poll interval in ms |
| `baseUrl` | `https://gitlab.com` | GitLab instance URL |
| `action_prompt_template` | (required for processing) | Maps GitLab action names to metadata templates |
| `groupPolicy` | `"disabled"` | Must be `"open"` for todos to flow |
| `senderPolicy` | `"allowlist"` | Who can trigger the bot |

## action_prompt_template

This field controls which todo actions are processed and how metadata is rendered. Only actions with a configured template are dispatched; all others are skipped and marked done.

```json
{
"action_prompt_template": {
"mentioned": "Project: %project% | Author: %author% | Title: %title%"
}
}
```

The `directly_addressed` action (comment starting with `@bot`) automatically falls back to the `mentioned` template if not explicitly configured.

### Available Action Keys

| Key | Trigger |
| --------------------- | ------------------------------------------------------------------------ |
| `mentioned` | Someone @mentions the bot in a comment or description (not at the start) |
| `directly_addressed` | A comment **starts with** `@bot` (falls back to `mentioned` template) |
| `assigned` | Someone assigns the bot to an issue/MR |
| `review_requested` | Someone requests the bot as a reviewer on an MR |
| `approval_required` | An MR requires the bot's approval (approval rules) |
| `marked` | Someone marks the bot's comment/issue/MR (star) |
| `build_failed` | A CI/CD pipeline fails on the bot's branch/MR |
| `unmergeable` | An MR the bot is involved with becomes unmergeable (conflicts) |
| `merge_train_removed` | An MR is removed from the merge train |

Only keys present in `action_prompt_template` are processed. Unconfigured actions are skipped and marked done silently.

### Template Variables

| Variable | Value |
| --------------- | --------------------------------- |
| `%project%` | Project path (e.g., `owner/repo`) |
| `%project_url%` | Full project URL |
| `%author%` | Todo author username |
| `%target_type%` | `Issue` or `MergeRequest` |
| `%iid%` | Issue/MR internal ID |
| `%title%` | Issue/MR title |
| `%description%` | Issue/MR description body |
| `%todo_id%` | GitLab todo ID |
| `%%` | Literal `%` (escape) |

Unknown variables are preserved as-is in the output.

### Prompt Assembly

The template renders into `envelope.metadata` (structured context). The triggering text (`todo.body` or description) goes into `envelope.text` (primary prompt). The base class assembles the final prompt sent to the agent:

```
[alice] please fix this bug

Project: owner/repo | URL: https://gitlab.com/owner/repo | Author: alice | Type: Issue | IID: 42 | Title: Test Issue | Description: ... | TodoID: 100
```

- Line 1: `[sender]` prefix + `envelope.text` (with `@bot` stripped)
- Line 3: `envelope.metadata` (rendered template, sanitized)

You do **not** need a `%body%` variable — the comment/description text is always the primary prompt content, and the template provides supplementary context below it.

## ⚠️ Security

On a **public project**, setting `senderPolicy: "open"` allows **any GitLab user** who @mentions the bot to submit prompts that drive the agent in your `cwd`.

Always use `senderPolicy: "allowlist"` with explicit `allowedUsers` on public projects.

## Mention Detection

The adapter detects mentions by scanning the text for `@bot-username` using a case-insensitive regex with word-boundary checks. The `isMentioned` flag is set on the envelope; the `GroupGate` (`requireMention` config) decides whether to process it.

## How It Works

The adapter uses GitLab's Todos API as the message source:

1. **Poll** `GET /todos?state=pending` for new todos
2. **Filter** by `updated_at > cursor` and configured `action_prompt_template`
3. **Detect mention type** via `target_url` anchor:
- `#note_123` present → comment mention → text is `todo.body` (the comment)
- No anchor → description mention → text is the issue/MR description
4. **Dispatch** the envelope through `handleInbound` (GroupGate applies mention policy)
5. **Advance cursor** and **mark todo done** (best-effort)

The cursor (`lastProcessedAt`) advances regardless of dispatch success or failure. Failed dispatches are logged but not retried — the user can re-mention the bot to trigger a new todo.

## Known Limitations

- **First start skips existing pending todos.** The cursor initializes to "now" on first launch.
- The bot does not read prior conversation history — only the triggering content is processed.
- Confidential (internal) notes are not filtered at the todo level; the todo body may contain internal comment text if the mention occurred in a confidential note.
- Requires `read_api` + `api` PAT scopes. Group-level or project-level tokens work if they have these scopes.
- Todos for Epics, Designs, and Alerts are skipped (only Issues and MRs are processed).

## Starting the Channel

```bash
qwen channel start my-gitlab
```
Loading
Loading