肆 · 通知
通知渠道
ctx.notify
与渠道无关的通知:业务包只调用 ctx.notify.send(),同一时间发往一个渠道(钉钉或飞书)。渠道由配置决定,也可以在运行中用 ctx.notify.use() 切换,不需要改业务代码。
接口
const r = await ctx.notify.send({
title: '需要人工复核',
markdown: '## 详情\n...', // 或 text: '纯文本',二选一
targets: { // 可选,按渠道给出目标,发送时只用当前渠道那一项;缺省用该渠道的 defaultTarget
dingtalk: { chatId: 'cid...' },
feishu: { chatId: 'oc_...' },
},
at: { // 可选,按渠道 @ 人(两边的用户 id 体系不同)
dingtalk: { userIds: ['...'] },
feishu: { userIds: ['ou_...'] },
},
idempotencyKey: 'evt_...', // 透传给渠道
traceId: 'evt_...',
signal,
})
// r: { channel: 'dingtalk' | 'feishu', results: Array<{ ok, messageId?, error? }> }业务包只需要 inject = ['notify']。发往哪个渠道由本行的配置决定:
- id: agent-kit-notify
disabled: false
config:
channel: feishu # dingtalk 或 feishu;对应的渠道行需要启用发送结果与错误
- 同一时间只发往一个渠道,不会在失败时自动改发另一个渠道。
- 发送完成时返回
{ channel, results }:channel是实际发往的渠道,results是该渠道的逐目标结果。多群时部分失败体现在results中,不抛错。 - 当前渠道的行没有运行(未启用或启动失败)时,抛出
NotifyError,code为channel_unavailable,details.channel为渠道名。 - 渠道自己发送失败时,抛出渠道的错误,例如
FeishuSendError的timeout、DingtalkSendError的exit_nonzero;按code与retryable判断即可。 - 渠道的重试、幂等与多目标行为不变,见 钉钉推送 与 飞书推送。
切换渠道
| 方式 | 何时生效 | 持续多久 |
|---|---|---|
修改配置的 channel(手动编辑 cordis.patch.yml,或 admin 包的设置页 / setup) | 保存后立即,原地生效 | 长期;覆盖此前 use() 的选择 |
ctx.notify.use('feishu') | 调用后立即 | 只在内存中;dsh 重启后回到配置值,期间修改配置时以新配置为准 |
ctx.notify.channel // 当前生效的渠道:'dingtalk' | 'feishu'
ctx.notify.use('dingtalk') // 运行中切换;传入其他值抛出 NotifyError(code: invalid_channel)要长期切换请改配置;use() 适合业务包自己的临时逻辑,例如当前渠道登录失效时先改用另一个渠道。
登录状态与登录
const s = await ctx.notify.status()
// {
// channel: 'feishu',
// source: 'config', // 'config':来自配置;'runtime':被 use() 切换过
// channels: {
// feishu: { channel: 'feishu', running: true, identity: 'user', online: false, detail: '…', checkedAt: 1758… },
// dingtalk: { channel: 'dingtalk', running: false, detail: 'agent-kit-dingtalk is not running' },
// },
// }status()对每个渠道:行在运行时,给出该渠道status()的实时结果(ChannelStatus)加running: true;没有运行时给出{ channel, running: false, detail }。login(channel?)/logout(channel?)转给当前渠道或指定的渠道,行为见 钉钉 与 飞书 的「登录状态与登录」。指定的渠道没有运行时抛出channel_unavailable。
login() 是设备流登录:拿到授权链接就返回,链接怎么交给要登录的人由业务包决定。例如发到运维群:
const s = await ctx.notify.login() // 当前渠道
await postToOps(`请在 15 分钟内打开链接完成登录:${s.verificationUrl}`)
const st = await s.completed // 授权完成后的状态;过期、拒绝或 s.cancel() 时 online 为 false 渠道与生命周期
本 Service 不 inject 钉钉与飞书,而是在每次发送时查找对应的 Service。原因是 cordis 4 的 inject 都是必需的:如果 notify inject 了渠道,停用任一渠道都会连带卸载 notify,以及所有只依赖 notify 的业务插件。
现在的行为是:
- 启用、停用渠道行只影响发送结果,不会重新加载 notify,也不会重新加载只 inject
notify的业务插件。 - 修改本行自己的
channel时,notify 在自己的internal/update钩子里原地换上新值,不重启(dsh 默认会在配置变更时重启插件,进而重启所有依赖它的插件;notify 拦截了这次重启)。新配置不合法时保存被拒绝,旧配置继续生效。 health():当前渠道没有运行或自身为failed时为failed;最近一次发送失败时为degraded。
什么时候直接用 ctx.dingtalk 或 ctx.feishu
需要某个渠道特有的能力时,例如钉钉的 openDingtalkId 目标或 webhook 身份,直接 inject 对应的 Service。只是「发一条通知」时,推荐用 ctx.notify,把渠道选择留给运维。
配置
在 Profile 的 cordis.patch.yml 中按 id agent-kit-notify 启用并给出配置。按 id 修改 config 时整段替换,未给出的字段使用默认值。下表由本包源码中的配置 schema 生成。
- id: agent-kit-notify
disabled: false
config:
# 只写需要改的字段,其余使用下表的默认值 | 字段 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|
channel 必填 | — | dingtalk | feishu | 通知发往的渠道:dingtalk 或 feishu |
约束与说明
- channel 所选渠道对应的行(agent-kit-dingtalk / agent-kit-feishu)需要启用;未运行时 send() 抛出 channel_unavailable,admin 包的 doctor 检查 agent-kit-notify.channel 也会失败。
- 修改 channel 时原地生效:notify 在自己的 internal/update 钩子里换上新值,拦截 cordis 默认的重启,notify 与只 inject notify 的业务插件都不重新加载。
- ctx.notify.use(channel) 在运行中切换,立即生效、只在内存中;dsh 重启后回到配置值,修改配置时以新配置为准。
- 本 Service 不 inject 钉钉与飞书,而是在发送时查找:启用、停用渠道行也不会重新加载 notify。
- status() 返回当前渠道、来源(config / runtime)与两个渠道的实时登录状态;login(channel?) / logout(channel?) 转给当前(或指定的)渠道。
错误码
所有错误都是 KitError:用 isKitError(e) 判断,按 code 与 retryable 决定是否重试。
| code | 可重试 | 含义 |
|---|---|---|
channel_unavailable | 否 | 当前渠道(或 login() / logout() 指定的渠道)对应的行未运行(未启用或启动失败);details.channel 为渠道名。 |
invalid_channel | 否 | use() 传入了 dingtalk / feishu 以外的渠道。 |
invalid_config | 否 | 配置或环境变量非法,Service 启动失败。 |