dsh-agent-kit
DeepSeek Harness plugin kit for always-on agent workers: WebSocket, DingTalk, Claude Code/Codex, Jev.
- Stars
- 0
- Language
- TypeScript
- Created
- Sep 23, 2026
- Updated
- Sep 28, 2026
Introduction
dsh-agent-kit
官网:https://byx-darwin.github.io/dsh-agent-kit/
构建常驻 Agent Worker 的 DeepSeek Harness 插件工具包:WebSocket 接入、钉钉 user 推送与通知、Claude Code / Codex 任务委托、TypeSafe Jev(或本地 Laya)校验。
状态:
0.2.0已发布到 npm:@baoyx/dsh-agent-kit与@baoyx/dsh-agent-kit-admin。 历史设计文档可能包含已移除的方案,以当前代码和官网文档为准。
为什么需要它
很多系统都需要一个"常驻 Agent Worker":
- 通过 WebSocket 接收业务系统推送的事件;
- 把告警类事件推送到钉钉;
- 把需要语义判断的任务交给 Claude Code、Codex 等 Agent 处理;
- 用 TypeSafe Jev 对 Agent 的结果做低成本校验,不通过时升级或转人工。
这四项能力与业务无关。@baoyx/dsh-agent-kit 把它们实现为一组 DeepSeek Harness(dsh)插件 Service,项目只需在自己的业务包里编写协议、路由、模板和流程。
两个包
| 包 | 目录 | 作用 |
|---|---|---|
@baoyx/dsh-agent-kit | 仓库根目录 | 给业务包用的库:五个 Service 与测试工具。业务包只依赖它 |
@baoyx/dsh-agent-kit-admin | admin/ | 可选的运维工具:dsh-agent-kit doctor / setup 命令行、dsh Web「设置 → Agent Kit」页、业务包设置入口的登记。见下文「运维工具(可选)」 |
不装 admin 包时,五个 Service 照常工作,配置直接写在 Profile 的 cordis.patch.yml 中。
功能
| Service | 作用 |
|---|---|
ctx.agentWs | WebSocket 客户端(仅 wss://):可刷新的鉴权请求头、心跳、指数退避重连、并发与背压控制;鉴权失败或指定关闭码时进入 failed 状态并可慢速重试 |
ctx.dingtalk | 通过已登录的钉钉 dws CLI,以 user 身份发送消息;统一监听群内 @ 消息,并按群 ID / 内容规则下发给业务插件;提供登录状态、设备流登录与退出 |
ctx.notify | 通知入口:业务包调用 ctx.notify.send(),由钉钉发送 |
ctx.agentTasks | 调用 dsh 已注册的 subagent provider(如 claude-code、codex)执行一次性任务:默认只读权限(无法由本包强制的 provider 需运维声明权限上限)、每个任务独立目录、类型化的 JSON Schema 输出、并发与排队上限 |
ctx.jev | 调用 TypeSafe Jev 或本地部署的 Laya(二选一),返回 Choice / Noul / Score 的类型化判断和概率 |
- 五个 Service 默认禁用,按需启用;未启用的 Service 不校验配置,也不影响其他 Service。
- 每个 Service 提供
health();进入failed时触发agent-kit/service-failed事件,便于接入外部监控。 - 所有错误都是
KitError,带code与retryable,调用方据此决定是否重试。
导入路径:根入口 @baoyx/dsh-agent-kit 导出全部 Service 类、配置、错误类型与工具函数;另有子路径 @baoyx/dsh-agent-kit/ws、/dingtalk、/notify、/agent-tasks、/jev、/secrets 和 /testing。
架构
┌──────────── dsh Profile(常驻进程)────────────┐
│ 你的业务包(协议、路由、模板、流程) │
│ │ inject │
│ ▼ │
│ @baoyx/dsh-agent-kit(各 Service 单独启用) │
│ agentWs · dingtalk · notify · agentTasks · jev│
│ │ │
│ ▼ │
│ dsh:subagent-claude-code / subagent-codex、 │
│ 子进程管理、Session 持久化、Web 界面 │
│ │
│ (可选)@baoyx/dsh-agent-kit-admin:设置页与 CLI │
└─────────────────────────────────────────────────┘
notify 不直接连接外部系统:它在每次发送时查找 dingtalk 并转发,业务包因此只需依赖 notify。
本包不包含任何业务协议、事件类型、提示词或分类体系,也不保存可靠事件状态;投递可靠性由对端系统负责。一条上游连接只应部署一个实例。
环境要求
- Node.js
^22.19或>=24 @deepseek-ai/dshCLI0.1.5-rc.3或0.1.7-alpha.2(均为预发布版本,dsh 与 cordis 相关依赖需锁定到与所用 dsh 一致的版本;以下示例以0.1.5-rc.3为准,使用 0.1.7 时把版本号一并替换)- 使用
ctx.dingtalk:已安装并登录dws(已验证dingtalk-workspace-cli@1.0.62),仅支持user身份 - 使用
ctx.notify:同时启用ctx.dingtalk - 本包运行时不会安装上述 CLI;可以手动安装,或用 admin 包的
setup在你确认后安装(见「运维工具(可选)」) - 使用
ctx.agentTasks:安装@deepseek-ai/dsh-subagent-claude-code和/或@deepseek-ai/dsh-subagent-codex(ctx.subagents与子进程服务由 dsh 的 base bundle 提供),并完成 Claude Code / Codex 的原生登录(provider 会剔除名字含 KEY / TOKEN / SECRET / PASSWORD 的环境变量,依赖这类变量鉴权时需在 provider 的 Configenv中显式给出) - 使用
ctx.jev:安装@typesafe-ai/sdk,并设置环境变量TYPESAFE_API_KEY;macOS 上也可以用keychainService从钥匙串读取,推荐与 gitflow-cli 等工具共享的服务名ai.typesafe.api-key(保存:security add-generic-password -a "$USER" -s ai.typesafe.api-key -U -w)。改用本地 Laya 时配置provider: laya与baseURL,不需要 TypeSafe Key - 生产部署:Profile 进程由 systemd、pm2 等进程守护托管
安装
npm i -g @deepseek-ai/dsh@0.1.5-rc.3
# 基于 web 模板创建一个常驻 Profile
dsh --profile my-agent --from-default-profile web
# 安装本包、Agent provider 和你的业务包
# subagent 包必须显式指定版本:其 latest 标签目前指向 0.0.1-rc.1,与 dsh 不匹配
dsh plugin --profile my-agent add @baoyx/dsh-agent-kit \
@deepseek-ai/dsh-subagent-claude-code@0.1.5-rc.3 \
@deepseek-ai/dsh-subagent-codex@0.1.5-rc.3
dsh plugin --profile my-agent add ./my-business-plugin-0.1.0.tgz
# 可选:运维工具(doctor / setup 命令行与 Web 设置页)
dsh plugin --profile my-agent add @baoyx/dsh-agent-kit-admin
# 在 Profile 的 cordis.patch.yml 中启用需要的 Service(见下文「配置」)
dsh --profile my-agent --dump-config # 检查各层是否生效
dsh --profile my-agent --no-open
编写业务包
业务包把本包声明为 peer 依赖,保证一个 Profile 中只加载一份实例:
{
"name": "@your-org/dsh-agent-your-project",
"type": "module",
"peerDependencies": {
"@deepseek-ai/cordis": "4.0.2",
"@baoyx/dsh-agent-kit": "^0.2.0"
},
"devDependencies": {
"@deepseek-ai/cordis": "4.0.2",
"@baoyx/dsh-agent-kit": "^0.2.0"
},
"dsh": { "bundle": { "patch": "./patch.yml" } }
}
@deepseek-ai/cordis 的版本与目标 dsh 依赖的版本保持一致(dsh 0.1.5-rc.3 对应 4.0.2,0.1.7-alpha.2 对应 4.0.4)。开发期可以用 link: 指向本包的本地 checkout。
插件示例(发通知推荐用 ctx.notify,由已登录的钉钉 user 身份发送):
import type { Context } from '@deepseek-ai/cordis'
import { choice, isKitError, noul, untrusted } from '@baoyx/dsh-agent-kit'
export const name = 'my-agent'
export const inject = ['agentWs', 'notify', 'agentTasks', 'jev']
export function apply(ctx: Context, config: Config) {
const conn = ctx.agentWs.connect<Frame>({
url: config.url,
headers: async () => ({ 'X-Token': await getToken() }), // 每次(重)连接前调用
parse: parseFrame, // 把 JsonValue 转成业务类型,抛错则丢弃该帧
concurrency: 2,
onMessage: async (frame, { signal }) => {
if (frame.type === 'alert') {
// 通知固定由已登录的钉钉 user 身份发送
await ctx.notify.send({
title: frame.title,
markdown: renderAlert(frame),
idempotencyKey: frame.id,
traceId: frame.id,
})
return
}
const draft = await ctx.agentTasks.run<Result>({
provider: 'claude-code',
title: `classify ${frame.id}`,
// 来自 WebSocket 的数据是不可信的,用 untrusted() 包裹,声明它不是指令
prompt: [CLASSIFY_INSTRUCTIONS, untrusted('event', frame.input)],
outputSchema: ResultSchema, // JSONSchemaType<Result>,draft.output 的类型为 Result
signal,
traceId: frame.id,
})
// Agent 输出同样不可信:触发副作用前按业务白名单校验
if (!CATEGORIES_ALLOWLIST.has(draft.output.category)) throw new Error('unexpected category')
const { answers } = await ctx.jev.judge({
state: { input: frame.input, candidate: draft.output },
questions: {
wrong: noul('候选结果与输入证据不符', { true: '不符', false: '相符' }),
pick: choice('输入最符合哪个类别', CATEGORIES),
},
signal,
traceId: frame.id,
})
// noul 为回答"是"的概率;choice 带 confidence 与各选项概率
await conn.send({ id: frame.id, result: draft.output, verified: answers.wrong.noul < 0.7 })
},
onError: (err, frame) => {
if (isKitError(err) && err.retryable) {
// 例如:记录下来,交给对端系统重投
}
},
})
}
通知与钉钉登录
ctx.notify 将消息转发给已启用的 ctx.dingtalk,返回 { channel: 'dingtalk', results }。钉钉行未运行时抛出 NotifyError(channel_unavailable);发送失败时抛出 DingtalkSendError。
await ctx.notify.send({
title: '需要人工复核',
markdown: body,
targets: { dingtalk: { chatId: 'cidxxxx' } },
idempotencyKey: frame.id,
})
钉钉仅支持已登录的 user 身份。ctx.dingtalk.status() 查询当前身份状态,login() 返回授权链接、验证码与完成状态,logout() 仅退出当前账号。admin 设置页可将授权链接显示为二维码;是否能真正授权取决于 dws 与钉钉组织权限。通知服务也提供 status()、login()、logout() 转发到钉钉。
业务插件可调用 ctx.dingtalk.searchRecipients('group' | 'user', '名称关键词'),从当前登录账号查找群或个人候选;结果含真实稳定 ID 与完整性标记,不自动选重名候选。业务插件登记设置字段 kind: 'dingtalk-target' 时,设置页提供同样的群名/姓名查找,选择后仅保存 { chatId }、{ userId } 或 { openDingtalkId } 到该插件自己的配置,再由插件决定哪些业务消息发往该目标。查找本身不发送消息;接收群 @ 的 groupRoutes 与主动发送目标互不绑定。
export const inject = ['dingtalk']
await ctx.dingtalk.send({ title: frame.title, markdown: renderAlert(frame), idempotencyKey: frame.id })
群内 @ 消息下发到业务插件
设置页的钉钉卡片可配置 groupRoutes(群 ID → 业务插件 ID),支持从未命中记录选群;群名只用于识别,持久匹配键仍是群 ID。业务插件用同一个 ID 注册处理器:
export const inject = ['dingtalk']
export async function apply(ctx: Context) {
const subscription = ctx.dingtalk.onPluginMessage('business-orders', async (message, { signal }) => {
await handleOrder(message.content, { signal, eventId: message.eventId })
})
await subscription.ready
}
保存 groupRoutes: [{ conversationId: 'cid-orders', pluginId: 'business-orders' }] 后,该群消息只进入指定插件;插件未注册时作为未命中记录,不回退给其他插件。业务插件登记到 admin 设置页的 ID 可作为输入建议,也允许手动输入尚未登记的 ID;仅登记业务行并不等于已经注册消息处理器。
兼容旧接口:业务插件也可调用 ctx.dingtalk.onMessage() 在代码里注册群路由;设置页已配置的群优先采用设置页指定的插件,不再走旧接口。Agent Kit 启动一个常驻的 dws event +listen-im --kind at-me -f ndjson 进程;旧接口按事件的 conversation_id 定位群,再用可选的 match 细分。同一事件只交给优先级最高的第一个匹配处理器,未命中时不广播。业务插件卸载时自动注销路由;监听会继续运行以发现尚无插件处理的消息,Service 卸载时退出。
export const inject = ['dingtalk']
export async function apply(ctx: Context) {
const subscription = ctx.dingtalk.onMessage(
{ conversationId: 'cid-orders', match: (m) => m.content.startsWith('工单'), priority: 10 },
async (message, { signal }) => {
// message.content 是不可信输入;业务插件负责校验和处理。
await handleOrder(message.content, { signal, eventId: message.eventId })
// 需要回复时,由业务插件决定是否调用 ctx.dingtalk.send()。
},
)
await subscription.ready
}
监听仅覆盖 @ 当前登录 user 的群消息,不监听群内所有消息。未命中的消息(包括尚无任何业务路由时)保留最近 200 条,记录群 ID、可查到的群名、时间、事件 ID 与最多 240 字正文预览;群名查询失败不影响群 ID。dsh Profile 下按 dws 账号分别保存于 .agent-kit/dingtalk-unmatched/<账号哈希>.json(目录 0700、文件 0600),设置页钉钉卡片可在本机查看,插件可调用 ctx.dingtalk.unmatchedMessages() 读取。不会自动转发给兜底插件。事件在进程内按 eventId 做有界去重;重启后的重复投递仍需业务插件按 eventId 保证幂等。dryRun: true 不允许启动真实监听。业务插件可用 subscription.close() 提前注销;处理器异常只影响该消息,不会把它转给下一个插件。
本地开发与测试
没有 dws 或 Agent 登录态时,可以把钉钉设为 dryRun: true,并使用 @baoyx/dsh-agent-kit/testing 导出的假 dws、本地 WebSocket 服务端、假 provider 与 Jev mock。
完整接口、错误码与配置默认值见 官网文档。
配置
各 Service 的可变参数都是 cordis.yml 中经过校验的配置字段。本包的 bundle 以禁用状态注册五个 loader 行(agent-kit-ws、agent-kit-dingtalk、agent-kit-notify、agent-kit-agent-tasks、agent-kit-jev),在 Profile 的 cordis.patch.yml 中按 id 启用并给出配置:
- id: agent-kit-ws
disabled: false
- id: agent-kit-dingtalk
disabled: false
config:
identity: user
defaultTarget: { chatId: cidxxxx }
- id: agent-kit-notify
disabled: false
config:
channel: dingtalk # 固定为钉钉;需要启用钉钉行
- id: agent-kit-agent-tasks
disabled: false
config:
workspaceDir: /var/lib/my-agent/tasks
declaredPermissions:
claude-code: read-only # claude-code 默认 permissionMode: dontAsk,不能执行命令或写文件
- id: agent-kit-jev
disabled: false # 需要 @typesafe-ai/sdk 与环境变量 TYPESAFE_API_KEY
config:
keychainService: [ai.typesafe.api-key, gitflow-cli-typesafe] # 可选:macOS 上未设置环境变量时按顺序从钥匙串读取;旧名仅用于迁移期
# 或改用本地部署的 Laya(与上面二选一):
# config:
# provider: laya
# baseURL: http://127.0.0.1:8000 # laya-serve 的地址
# apiKeyRef: LAYA_API_KEY # 可选:Laya 开启鉴权时从该环境变量 / dsh 凭据读取 Key
按 id 修改 config 时整段替换;未给出的字段使用默认值。
| Service | 主要字段 |
|---|---|
agentWs | pingIntervalMs、readTimeoutMs、reconnect.initialDelayMs、reconnect.maxDelayMs、reconnect.jitter、stableResetMs、fatalRetryDelayMs、maxPayloadBytes、maxPendingMessages |
dingtalk | identity(只允许 user)、defaultTarget、dwsPath、timeoutMs、killGraceMs、retry.maxAttempts、preflightIntervalMs、dryRun |
notify | channel(只允许 dingtalk) |
agentTasks | workspaceDir(必填)、defaultTimeoutMs、maxConcurrency、maxQueueSize、keepWorkdir、declaredPermissions、toolAllowlist |
jev | provider、baseURL、apiKeyRef、model、timeoutMs、keychainService、keychainAccount |
agentTasks.declaredPermissions:claude-code、codex 不支持按任务过滤工具,权限由 provider 实例自己的配置决定。运维在这里声明其实际权限上限(read-only/workspace-write);未声明或上限高于任务请求的档位时,任务以unsupported_permissions失败。- 代码任务可由业务插件调用
ctx.agentTasks.run({ ..., worktree: { repository: '/绝对路径/仓库' } })。Kit 从干净仓库的HEAD创建独立工作树与agent-kit/task-<taskId>分支,返回路径与分支;不会自动提交、推送、合并或删除。三个具体项目路径和任务的权限申请属于下游业务插件配置,Kit 设置页不硬编码项目或 provider 列表。 notify.channel固定为dingtalk;钉钉行未启用时发送报channel_unavailable。dingtalk.dwsPath必须指向可直接执行的文件(可执行二进制、.exe或.js);不支持 Windows 的.cmd/.bat包装脚本。
TypeSafe Key(TYPESAFE_API_KEY)按以下优先级解析,三个平台的落盘位置不同:
| 来源 | 说明 |
|---|---|
| 环境变量 | 进程自身的 TYPESAFE_API_KEY,优先级最高,admin 包的 CLI/Web 均不会覆盖或清除它 |
| macOS 钥匙串 | 通过 security 命令读写,服务名默认 ai.typesafe.api-key(keychainService 可配置为数组做迁移期兼容);仅 macOS 可用 |
| dsh 凭据文件 | $DSH_HOME/.credentials.yaml,由 @deepseek-ai/dsh-credentials-local 管理;Linux/其他平台的默认落盘位置,macOS 上作为钥匙串之外的第二选择 |
安全
- 密钥只从环境变量、macOS 钥匙串或 dsh 凭据文件读取,且不传给本包启动的子进程;钉钉凭据由
dws自己的登录态管理,本包不保存。 - 启动
dws时不经过 shell,子进程只继承环境变量白名单。 - 本包运行时从不安装任何 CLI;只有 admin 包的
setup在你确认后才运行锁定版本的npm i -g。 - 设备流登录的授权链接只交给调用
login()的业务包,本包不会把它发到任何地方;把链接发给谁、发到哪个群由业务包决定,请只发给应当登录的人。 - 来自 WebSocket 的数据和 Agent 的输出都视为不可信:Agent 默认只读,在每个任务独立的空目录中运行;输出经过 Schema 与业务白名单校验后才应触发副作用。
- 只允许
wss://,不关闭证书校验,不跟随重定向。 - 日志和错误经过统一脱敏,不记录鉴权头、API Key 和完整消息正文。
- dsh Web 界面中保存了完整的 prompt 与输出,只应绑定
127.0.0.1或放在鉴权代理之后。 - 发送给 Claude Code、Codex、TypeSafe 的内容由业务包决定,请在业务包中做数据最小化。
运维工具(可选):@baoyx/dsh-agent-kit-admin
@baoyx/dsh-agent-kit-admin 提供配置引导与运维界面。它读写的仍然是 Profile 的 cordis.patch.yml,不装它不影响任何 Service。业务包不需要依赖它(只有用到下文的运行时登记时,Profile 里才必须有它)。
dsh plugin --profile my-agent add @baoyx/dsh-agent-kit @baoyx/dsh-agent-kit-admin
admin 包以常驻行 agent-kit-admin 注册(ctx.agentKitAdmin,即设置页调用的服务端接口),并让 dsh 发现它的前端模块;它不连接任何外部服务,也不影响基础包各行的启停。
doctor 与 setup
admin 包自带 CLI dsh-agent-kit(随包安装到 node_modules/.bin),不需要启动 dsh:
# 交互式生成/修改 cordis.patch.yml(带 diff 预览,需确认后才写入)
npx @baoyx/dsh-agent-kit-admin setup --profile my-agent
# 体检:Node 版本、dws 安装与登录态、Agent provider、TypeSafe Key 等是否满足已启用 Service 的要求
npx @baoyx/dsh-agent-kit-admin doctor --profile my-agent
setup 的流程:选择 Profile → 勾选要启用的 Service → 安装缺少的 dws → 逐个配置 → 预览 diff 并确认写入 → 自动运行 doctor。
- 安装:如果启用了钉钉而
dws不在PATH中,setup会展示npm i -g dingtalk-workspace-cli@1.0.62,确认后安装。 - 钉钉:固定
user身份;未登录时可以拉起dws auth login,可配置默认目标与dryRun。 - 通知:固定发往钉钉,钉钉行未启用时会提示。
setup 只会修改 Profile 目录下的 cordis.patch.yml;doctor 只读,不修改任何文件,--json 输出机器可读的体检报告,可接入 CI。doctor 的 agent-kit-notify.channel 检查在所选渠道的行未启用时失败。两者都会在 $DSH_HOME/profiles 下扫描带有基础包(bundles 里含 @baoyx/dsh-agent-kit)的 Profile;只有一个候选时自动选中,有多个时需要 --profile 指定。
设置页
Profile 中装了 admin 包并启动后,dsh Web「设置 → Agent Kit」里有五个 Service 卡片。钉钉仅支持 user 身份,通知固定发往钉钉;可以启停、编辑配置、查看健康状态与体检结果,效果与 CLI 等价:
- 保存时按乐观并发(
version)比较,若与他人(包括另开终端手改cordis.patch.yml)冲突会提示「已被修改」并自动刷新为最新内容,需要重新确认后再保存。 - 只读闸门默认关闭(fail closed):只有 dsh Web 确认自己绑定在
webServer.host === '127.0.0.1'时设置页才可写;未绑定回环地址(例如以--host 0.0.0.0之类的方式对外暴露)或压根没加载webServer服务时,设置页一律降级为只读,避免把配置写入接口暴露给公网。dsh CLI 自身也拒绝--host 0.0.0.0("it would expose remote code execution to the network"),二者是两道独立的防线。 - TypeSafe Key 只能写入、不能在页面或接口响应中读出;页面只显示「已配置(来源:环境变量/钥匙串(服务名)/dsh 凭据文件,均为本地化文案,非原始值)」或「未配置」。CLI 与 Web 保存 Key 时会提示当前平台可选的目标(
keychain/credentials),行为一致。 - 钉钉卡片提供
user扫码登录:页面在本地把 CLI 的设备授权链接生成二维码,同时展示验证码和备用链接;扫码授权后自动更新状态,也可以取消或退出。若 dws 提示「暂无 CLI 数据访问权限」,需由钉钉组织主管理员开启该权限。
把业务包的配置接入设置页与 doctor
业务包自己的 loader 行(连接地址、token 引用、通知目标、功能开关……)可以登记到 admin 包的设置页、agentKitAdmin、doctor 和密钥管理,运维在一个页面里配完整个 Profile,不需要再写第二个设置页。这些登记只在 Profile 装了 admin 包时生效。
推荐在 package.json 里声明静态清单:业务插件未启用、配置有误或启动失败时,卡片、表单和检查照样出现,doctor 也从这里发现业务行。
{
"dsh": {
"bundle": { "patch": "./patch.yml" },
"agentKit": { "entries": ["./lib/agent-kit-entry.js"] }
}
}
清单模块必须无副作用(不要在顶层连接网络、读取环境),默认导出一个或一组条目。条目类型与 defineEntry(只做类型推断,原样返回参数)从 @baoyx/dsh-agent-kit-admin/entry 导入,业务包把 admin 包加到 devDependencies 即可:
// src/agent-kit-entry.ts
import { defineEntry } from '@baoyx/dsh-agent-kit-admin/entry'
import { Config } from './config.js' // Schemastery schema
export default defineEntry({
id: 'my-agent', // cordis.patch.yml 中的行 id
label: '我的业务',
schema: Config, // 先用 schema 校验并补默认值
validate: (c) => [ // schema 表达不了的约束;path 相对本行 config
...(c.url.startsWith('wss://') ? [] : [{ path: 'url', message: '必须是 wss://' }]),
...(c.batchSize > c.maxInFlight ? [{ path: 'batchSize', message: '不能大于 maxInFlight' }] : []),
],
fields: [
{ path: 'url', label: '上游地址' },
{ path: 'alertTarget.chatId', label: '告警群 ID' },
{ path: 'batchSize', label: '批大小', kind: 'number' },
{ path: 'mode', label: '模式', kind: 'select', options: ['shadow', 'live'] },
],
// ref 可以来自配置:保存新的 tokenEnv 后,设置页和 setSecret 跟随新名字
secrets: [{ label: '上游 Token', refFrom: 'tokenEnv', ref: 'MY_AGENT_TOKEN' }],
service: 'myAgent', // 行运行中时,从 ctx.myAgent.health() 读取状态
dependsOn: ['agent-kit-ws', 'agent-kit-notify'], // 停用这些行前,设置页会提示连带停止本行
checks: async (ctx) => [
{ id: 'upstream', title: '上游可达', status: 'pass', detail: 'ok' },
],
})
也可以在插件里运行时登记(插件需 inject agentKitAdmin,因此 Profile 中必须装有 admin 包;卸载时自动注销;同 id 时覆盖清单,例如补上 health):
export const inject = ['agentWs', 'agentKitAdmin']
export function apply(ctx: Context) {
ctx.agentKitAdmin.registerEntry({ ...entry, health: () => ({ status: 'ok', detail: `${conn.state}` }) })
}
- 保存业务行与基础包的行共用同一套版本冲突检测;只替换被保存的那一行,其他行的文本逐字节不变。
- 校验失败时返回
invalid_config和按字段的错误(path为<行 id>.<字段>),与基础包自己的行一致。 - 业务行的密钥只写入 dsh 凭据文件(
$DSH_HOME/.credentials.yaml),插件运行时用ctx.credentials.resolve(ref)或基础包导出的resolveSecretRef(ref)(@baoyx/dsh-agent-kit或@baoyx/dsh-agent-kit/secrets)读取(环境变量优先)。 - 业务行被停用时照常检查:配置错误记为警告,不让
doctor因一个未启用的行失败;启用后记为失败。
路线图
- 完成设计文档中的「实施前需核实」项
- 五个 Service 的首个实现与测试
- 钉钉 user 推送与通知(
ctx.dingtalk、ctx.notify) - 渠道登录状态与设备流登录(
status()/login()/logout()) - 运维工具拆分为可选的
@baoyx/dsh-agent-kit-admin - 发布
0.2.0到 npm - 通用的「Agent 执行 → Jev 校验 → 升级」级联 helper
- 钉钉卡片消息
开发
npm install
npm run typecheck
npm test # 单元 + 集成测试(集成测试会先构建 lib/,并在真实 dsh loader 中加载 patch.yml)
npm run test:coverage
npm run test:e2e # 端到端冒烟,按环境变量启用,见 tests/e2e/smoke.test.ts
admin 包在 admin/ 目录下,有自己的 npm install、npm run typecheck、npm test。