Qwen Code GitLab 渠道接入指南:基于 Todos API 的 Issue/MR 智能响应机器人
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
本篇指南讲解如何在 Qwen Code 中配置一个 GitLab 渠道(channel),让运行在终端中的 AI 编程代理能够监听 GitLab Todos、响应 Issue 与 Merge Request 中的 @ 提及、指派和评审请求,并自动在对应议题下回帖执行代码任务。读完本文,你将掌握 GitLab 渠道的 Token 准备、settings.json完整配置、action_prompt_template事件过滤与模板渲染机制、groupPolicy/senderPolicy安全模型、轮询与游标原理,以及启动渠道的 CLI 命令。
渠道定位:GitLab 作为消息源
GitLab 渠道(@qwen-code/channel-gitlab,实现于 packages/channels/gitlab/src/GitlabAdapter.ts)是一个轮询型(polling)渠道适配器。它并不监听 Webhook,而是以 GitLab 的Todos API为消息源,周期性拉取state=pending的待办事项,把"某人在 Issue/MR 中 @ 了机器人"这类事件转换成代理可执行的 prompt,再把代理的回复通过 Notes API 回帖到对应 Issue 或 MR 上。
从源码看,适配器继承自PollingChannelBase(见 packages/channels/base/src/PollingChannelBase.ts),底层通过@gitbeaker/rest(依赖声明见 packages/channels/gitlab/package.json)访问 GitLab REST API,并使用 zod 对持久化游标做运行时校验。
前提条件
- 一个 GitLab 账号(建议使用专用机器人账号,避免与个人操作混淆);
- 一个具备
read_api与api两个 scope 的Personal Access Token(PAT):read_api用于读取 todos 与项目数据,api用于在 Issue/MR 上发布评论(notes)。
创建 Token
- 进入 GitLabPreferences → Access Tokens;
- 勾选
read_api与api两个 scope 创建 Token; - 将 Token 安全地保存为环境变量,不要硬编码进配置文件。
从源码实现看,连接阶段会调用
Users.showCurrentUser()获取机器人自身的用户名(GitlabAdapter.ts),用于后续的 @ 提及识别与"跳过自己发起的 todo"判断,因此 Token 必须对应用户本身。
基础配置
在~/.qwen/settings.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%" } } } }设置环境变量:
export GITLAB_TOKEN="glpat-your_token_here"自托管 GitLab
对于自托管实例,通过baseUrl指定实例地址:
{ "baseUrl": "https://gitlab.example.com" }源码在connect()中会把baseUrl尾部的斜杠剥掉(replace(/\/+$/, '')),默认值为https://gitlab.com(GitlabAdapter.ts);测试用例也验证了带尾部斜杠的https://gitlab.example.com/会被归一化为无斜杠形式后再构造 Gitlab 客户端(见 GitlabAdapter.test.ts)。
配置选项一览
| Option | Default | Description |
|---|---|---|
token | (required) | PAT withread_api+apiscopes |
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","allowlist"with the project listed, or"pairing"with the project approved |
senderPolicy | "allowlist" | Who can trigger the bot |
几个参数的关键行为补充:
pollInterval:轮询间隔(毫秒)。PollingChannelBase中对其做了合法性校验——必须是大于 0 的有限数值,否则回退到默认值60000(见 PollingChannelBase.ts)。action_prompt_template:处理 todo 的前提。源码在connect()与pollOnce()两处都做了检查:若未配置或为空对象,渠道会打印warning: action_prompt_template is not configured; no todos will be processed,并直接跳过轮询(GitlabAdapter.ts)。测试用例也专门覆盖了"空模板不调用 TodoLists API"这一行为(GitlabAdapter.test.ts)。groupPolicy:见下文专门小节,默认值"disabled"会静默丢弃所有提及。
action_prompt_template:事件过滤器与元数据模板
该字段是 GitLab 渠道的真正事件过滤器:只有配置了模板的 action 才会被分发处理,其余 action 一律跳过并标记为 done(静默消费,不产生任何输出)。
{ "action_prompt_template": { "mentioned": "Project: %project% | Author: %author% | Title: %title%" } }directly_addressed(以@bot开头的评论)在未显式配置时,自动回退使用mentioned模板——这一回退逻辑实现在resolveTemplate()中:先精确查找templates[actionName],若 action 为directly_addressed且无显式模板,则返回templates['mentioned'](GitlabAdapter.ts),对应的行为测试见 GitlabAdapter.test.ts。
可用 Action Key
| Key | Trigger |
|---|---|
mentioned | Someone @mentions the bot in a comment or description (not at the start) |
directly_addressed | A commentstarts with@bot(falls back tomentionedtemplate) |
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 |
只有出现在action_prompt_template中的 key 会被处理;未配置的 action 会被跳过并静默标记为 done。
模板变量
| Variable | Value |
|---|---|
%project% | Project path (e.g.,owner/repo) |
%project_url% | Full project URL |
%author% | Todo author username |
%target_type% | IssueorMergeRequest |
%iid% | Issue/MR internal ID |
%title% | Issue/MR title |
%description% | Issue/MR description body |
%todo_id% | GitLab todo ID |
%% | Literal%(escape) |
模板渲染由buildMetadata()完成:以%%或%(\w+)%正则替换变量,%%转义为字面%,未知变量原样保留在输出中(GitlabAdapter.ts)。测试用例如下:
- 已知变量全部替换:
'Project: owner/repo | URL: https://gitlab.com/owner/repo | Author: alice | Type: Issue | IID: 42 | Title: Test Issue | TodoID: 100'; - 未知变量保留:
'Known: owner/repo Unknown: %nonexistent%'。
(见 GitlabAdapter.test.ts)
%description%的取值有额外语义:对于评论型提及(带#note_锚点),只有模板中包含%description%时才会额外调用Issues.show/MergeRequests.show抓取完整描述(needsDescription判断,GitlabAdapter.ts),抓取结果还会按chatId/targetType/iid做内存缓存。也就是说,把%description%从模板中移除可以省掉一次 API 调用。
Prompt 组装:主提示与结构化元数据分离
模板渲染结果进入envelope.metadata(结构化上下文),触发文本(todo.body评论内容,或描述)进入envelope.text(主提示)。基类最终组装出发送给代理的完整 prompt,形如:
[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- 第 1 行:
[sender]前缀 +envelope.text(其中@bot已被剥除); - 第 3 行:
envelope.metadata(渲染后的模板,已做清洗)。
无需%body%变量——评论/描述文本永远是主提示内容,模板只是其下方的补充上下文。@bot的剥离由stripBotMention()完成:它用(?<=\s|^|[([{<:;"'])\@username(?=[^a-zA-Z0-9_./-]|$)正则(大小写不敏感)匹配并移除提及(见 packages/channels/gitlab/src/mention.ts)。
安全模型:senderPolicy 与 groupPolicy
⚠️ senderPolicy 风险
在公开项目上,若设置senderPolicy: "open",任何 GitLab 用户只要 @ 提及机器人,就能提交 prompt 驱动你cwd目录中的代理执行操作。
- 公开项目上务必使用
senderPolicy: "allowlist"并显式列出allowedUsers; - 连接时,
allowedUsers会被统一转成小写并与发送者 ID 比对(GitlabAdapter.ts)。
⚠️ groupPolicy 下的项目级授权
需要注意:在groupPolicy: "pairing"下,授权是按项目授予的——一旦某项目通过审批,任何 GitLab 用户都能通过该项目的 Issue/MR 驱动机器人。GitLab 的所有流量都属于"群组流量",因此senderPolicy与allowedUsers并不会拦截已批准项目的成员。
审批以项目路径(owner/repo)为键,项目重命名或迁移后该键会变化——任何项目重命名、转移或删除之后,都应撤销过期的群组审批。
groupPolicy 必须是 "open"、"allowlist" 或 "pairing"
groupPolicy必须设置为"open"、"allowlist"(且项目被显式列出)或"pairing",todo 才会被处理:
"pairing":来自未批准项目的首次提及会创建一个群组配对请求;用qwen channel pairing approve审批一次后,该项目产生的 todos 就会持续被分发;- 默认值
"disabled":丢弃所有提及——todo 被标记为 done、游标照常推进,但不会发生任何分发。此时日志会记录preflight rejected reason=group_disabled,todo 仍被消费; - 如果你的机器人不响应提及,先检查
groupPolicy是否还是"disabled"。
源码在connect()时就会对非open/allowlist/pairing的取值打印告警(GitlabAdapter.ts),测试也验证了disabled会触发告警而pairing不会(GitlabAdapter.test.ts)。群组门控的完整求值顺序(disabled→ allowlist → pairing → mention gating)实现在 packages/channels/base/src/GroupGate.ts 的check()中。
工作原理:轮询、游标与分发
GitLab 适配器以 Todos API 为消息源,完整流程如下:
- 轮询
GET /todos?state=pending获取待办; - 首轮排空(first-poll drain):若游标从未初始化(
initialized: false),所有现存 pending todos 会被直接标记为 done、不做分发,游标推进到最大 todo ID——防止首次启动时积压洪峰; - 清理过期 todo:
id <= cursor的 todo 被 best-effort 标记为 done,避免每轮轮询重复拉取; - 过滤:仅保留
id > cursor且 action 命中action_prompt_template的 todo; - 识别提及类型:通过
target_url的锚点判断——- 含
#note_123→ 评论提及,文本取todo.body(即评论内容); - 无锚点 → 描述提及,文本取 Issue/MR 的描述;
- 含
- 分发:通过
handleInbound发出 envelope(要求groupPolicy为"open"、或"allowlist"且项目已列出、或"pairing"且项目已批准); - 推进游标并best-effort 标记 todo done。
上述逻辑对应源码中的pollOnce()(GitlabAdapter.ts)。几个值得注意的细节:
- 游标(cursor):结构为
{ lastProcessedId: number, initialized: boolean },首次启动初始化为{ lastProcessedId: 0, initialized: false };每个轮询周期结束会通过saveCursor()原子写入(先写.tmp再 rename)到~/.qwen/channels/<name>-<hash>-poll-cursor.json(见 PollingChannelBase.ts)。测试专门验证了 15 万条积压 todo 在首轮会被全部排空且零分发(GitlabAdapter.test.ts)。 - 游标与分发解耦:无论分发成功还是失败,
lastProcessedId都会推进。分发失败会在 Issue/MR 上回帖 ⚠️ 错误评论(内容为⚠️ Failed to process this request. Please re-mention the bot to retry.),不会重试——用户需要再次 @ 机器人来触发新 todo。 - 自提及跳过:todo 作者就是机器人本人时直接跳过(
todo.author.username === this.botUsername),防止机器人回复自己的评论形成死循环(GitlabAdapter.ts)。 - 非 Issue/MR 目标跳过:Epic、Design、Alert 等目标类型一律跳过;
target_url中的描述会先经fetchDescription()抓取(按chatId/type/iid缓存)。 - envelope 固定标记
isMentioned = true:因为 GitLab 在创建 todo 时已经确定了提及关系,适配器无需自行判断;@bot提及在分发前通过stripBotMention从消息文本中剥离(GitlabAdapter.ts)。 - 轮询健壮性:
PollingChannelBase内置指数退避——轮询异常时按2s → 4s → … → 30s递增退避并写日志,成功后复位(PollingChannelBase.ts)。
回帖的线程模型
代理回复通过sendThreadMessage落回 GitLab:threadId必须是issue:<iid>或mr:<iid>格式(例如issue:42、mr:7),否则抛错;回复分别走IssueNotes.create或MergeRequestNotes.create(GitlabAdapter.ts)。因此sessionScope建议保持chat_thread,让同一 Issue/MR 的多次交互落在同一会话上下文中。
响应反馈:👀 工作状态表情
对于已接受的评论提及(带#note_锚点的 note),渠道会在代理工作时给该 note 添加 👀 award emoji,待运行完成、失败或被取消后移除:
- 添加与移除都是 best-effort:award emoji API 或权限失败只会写日志,绝不会阻塞最终回复;
- 描述提及(无
#note_锚点)因为没有具体的 note 可以"点赞",不会添加 award emoji。
实现上,onPromptStart通过IssueNoteAwardEmojis.award/MergeRequestNoteAwardEmojis.award(emoji 名eyes)添加,onPromptEnd通过remove移除,并以reactionsMap 按messageId(即 todo id)管理状态(GitlabAdapter.ts);"不重复添加""失败不影响移除""MR 使用 MR 的 emoji API"等行为均有对应测试覆盖(GitlabAdapter.test.ts)。
已知限制
- 首次启动跳过存量 pending todos:游标首启为
{ lastProcessedId: 0, initialized: false },首个轮询周期会把所有已存在的 pending todos 标记为 done 而不分发(由initialized标志控制这一次性排空),避免积压洪峰; - 不读取历史对话:机器人只处理触发它的那一条内容,不携带此前会话历史;
- 机密(internal)note 泄露风险:若有人在机密 note 中 @ 机器人,todo body 会包含内部文本并被代理处理;而机器人的回复总是以公开 note 发布,可能暴露内部讨论。GitLab 的 todo API 不暴露 note 可见性,适配器无法过滤——避免在机密 note 中 @ 机器人;
- 需要
read_api+apiPAT scope;拥有这两个 scope 的群组级或项目级 token 同样可用; - Epic、Design、Alert 的 todos 会被跳过,仅处理 Issue 与 MR。
启动渠道
qwen channel start my-gitlab启动后渠道即按pollInterval周期轮询。若机器人不响应提及,优先按以下顺序排查:action_prompt_template是否配置了对应 action、groupPolicy是否为"disabled"、senderPolicy/allowedUsers是否拦截了发送者、Token 是否具备read_api+apiscope。更完整的渠道管理与安全配置说明可参考 docs/users/features/channels 目录下的其他渠道文档。
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考