dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
官网: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":
这四项能力与业务无关。@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.feishu · 飞书机器人(自建应用):发送文本或 Markdown 卡片;长连接接收群内 @ 机器人与私聊机器人的消息,按会话 ID 下发给业务插件 ctx.wecom · 企业微信智能机器人:以 Markdown 发送消息,长连接接收群内 @ 机器人与单聊消息,按会话分发;可直接回复收到的消息 ctx.notify · 通知入口:业务包调用 ctx.notify.send(),默认发往配置的默认渠道(钉钉、飞书或企业微信),也可在 targets 中按渠道指定 ctx.agentTasks · 调用 dsh 已注册的 subagent provider(如 claude-code、codex)执行一次性任务:默认只读权限(无法由本包强制的 provider 需运维声明权限上限)、每个任务独立目录、类型化的 JSON Schema 输出、并发与排队上限 ctx.jev · 调用 TypeSafe Jev 或本地部署的 Laya(二选一),返回 Choice / Noul / Score 的类型化判断和概率
health();进入 failed 时触发 agent-kit/service-failed 事件,便于接入外部监控。KitError,带 code 与 retryable,调用方据此决定是否重试。导入路径:根入口 @baoyx/dsh-agent-kit 导出全部 Service 类、配置、错误类型与工具函数;另有子路径 @baoyx/dsh-agent-kit/ws、/dingtalk、/feishu、/wecom、/notify、/agent-tasks、/jev、/secrets 和 /testing。
┌──────────── dsh Profile(常驻进程)────────────┐
│ 你的业务包(协议、路由、模板、流程) │
│ │ inject │
│ ▼ │
│ @baoyx/dsh-agent-kit(各 Service 单独启用) │
│ agentWs · dingtalk · feishu · wecom · │
│ notify · agentTasks · jev │
│ │ │
│ ▼ │
│ dsh:subagent-claude-code / subagent-codex、 │
│ 子进程管理、Session 持久化、Web 界面 │
│ │
│ (可选)@baoyx/dsh-agent-kit-admin:设置页与 CLI │
└─────────────────────────────────────────────────┘notify 不直接连接外部系统:它在每次发送时查找对应的渠道 Service 并转发,业务包因此只需依赖 notify。
本包不包含任何业务协议、事件类型、提示词或分类体系,也不保存可靠事件状态;投递可靠性由对端系统负责。一条上游连接只应部署一个实例。
^22.19 或 >=24@deepseek-ai/dsh CLI 0.1.5-rc.3、0.1.7-alpha.2 或 0.2.1-rc.2(均为预发布版本,dsh 与 cordis 相关依赖需锁定到与所用 dsh 一致的版本;以下示例以 0.1.5-rc.3 为准,使用其他版本时把版本号一并替换)0.2 起,某一行配置有误时 dsh 不再整体启动失败:只有这一行不运行,其余照常启动。请用 doctor 或设置页查看各行状态ctx.dingtalk:已安装并登录 dws(已验证 dingtalk-workspace-cli@1.0.62),仅支持 user 身份ctx.feishu:安装 @larksuiteoapi/node-sdk(^1.76.0),在飞书开发者后台创建自建应用并开启机器人能力,事件订阅选长连接并订阅「接收消息 v2.0」,开通 im:message:send_as_bot、im:message.group_at_msg:readonly、im:message.p2p_msg:readonly;App Secret 用 appSecretRef 引用(默认 FEISHU_APP_SECRET)。ctx.wecom:安装 @wecom/aibot-node-sdk(^1.0.7);在企业微信管理后台为智能机器人开启 API 模式并选择长连接;Secret 用 secretRef 引用(默认 WECOM_BOT_SECRET)。主动发送前需对方先给机器人发过消息。ctx.notify:同时启用默认渠道(钉钉、飞书或企业微信)的 Servicesetup 在你确认后安装(见「运维工具(可选)」)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 的 Config env 中显式给出)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 Keynpm 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,0.2.1-rc.2 对应 4.0.5)。开发期可以用 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') {
// 通知发往默认渠道
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.send() 未指定 targets 时发往默认渠道(agent-kit-notify 的 channel),使用该渠道配置的默认目标;targets 中写了哪些渠道就只发这些渠道,并行发送。返回 { deliveries: Array }。只有两种情况直接抛出 NotifyError:未指定 targets 且默认渠道未运行(channel_unavailable);targets 中出现未知渠道(invalid_channel)。其他失败(指定的渠道未运行、渠道发送失败)都在对应的 results[i].error 中返回,调用方需要逐条检查。
await ctx.notify.send({
title: '需要人工复核',
markdown: body,
targets: { dingtalk: { chatId: 'cidxxxx' } },
idempotencyKey: frame.id,
})
// 钉钉发往配置的默认群,飞书发往指定群
await ctx.notify.send({ text: '库存告警', targets: { dingtalk: 'default', feishu: { chatId: 'oc_xxx' } } })targets 中某渠道写 'default',表示发往该渠道并使用其配置的默认目标。
钉钉仅支持已登录的 user 身份。ctx.dingtalk.status() 查询当前身份状态,login() 返回授权链接、验证码与完成状态,logout() 仅退出当前账号。admin 设置页可将授权链接显示为二维码;是否能真正授权取决于 dws 与钉钉组织权限。通知服务的 status() 返回 { defaultChannel, channels };login(channel) / logout(channel) 必须指定渠道,渠道不需要登录时抛出 login_unsupported。
业务插件可调用 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 })每个渠道 Service 都实现 ChannelService(从根入口导入类型):send()、status()、health(),需要登录的身份另外实现 login() / logout()。接收群内 @ 消息的渠道实现 ChannelInbox 接口,提供同样的四个方法:
onMessage({ conversationId, match?, priority? }, handler):按会话注册处理器,每条消息只交给优先级最高的首个匹配处理器;处理函数签名为 (message, { signal, reply });onPluginMessage(pluginId, handler):配合设置页的「会话 → 插件」路由;unmatchedMessages():最近未命中任何路由的消息摘要;searchRecipients(kind, query):按名称查找可作为发送目标的群或个人候选(飞书机器人只能搜群;企业微信只能在说过话的会话里查,名称就是会话 ID)。处理函数的第二个参数 { signal, reply }:signal 在超时、注销或服务停止时触发;reply(options) 回复这条消息,参数与 send() 相同(target 由 Service 决定,at 为该渠道自己的结构),失败写在返回结果里,不抛错。三个渠道的回复方式:
conversationId 以 cidme 开头)改为回复发送者的 openDingtalkId——这条规则是推断的,尚未用真实事件验证;chat_id;消息至少包含 ChannelAtMessage 的字段:eventId、messageId?、conversationId、senderId?、content、timestamp?,渠道特有字段另加(如钉钉的 senderOpenDingtalkId、飞书与企业微信的 chatType)。正文是外部输入,业务插件必须自行校验。
notify.send() 返回值由 { channel, results } 改为 { deliveries: Array }。DingtalkSendError)不再从 notify.send() 抛出,改为出现在对应 results[i].error 中。notify.use();要换默认渠道请改配置,临时发往其他渠道请在 targets 中指定。notify.status() 返回 { defaultChannel, channels },不再有 channel、source。notify.login() / logout() 必须传入渠道名。senderId(与 senderOpenDingtalkId 相同),原有字段不变。notify.channel getter 已删除,改用 notify.defaultChannel。signal 中止后,notify.send() 不再 reject,中止会作为对应渠道结果中的错误返回(钉钉为 code: 'aborted')。^0.2.0 不会自动升级到 0.3,需要改为 ^0.3.0。设置页的钉钉卡片可配置 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() 提前注销;处理器异常只影响该消息,不会把它转给下一个插件。
ctx.feishu 以飞书机器人(自建应用)身份发送消息,并通过长连接接收群内 @ 机器人与私聊机器人的消息。在 Profile 的 cordis.patch.yml 中启用 agent-kit-feishu:
- id: agent-kit-feishu
disabled: false
config:
appId: cli_xxxx # App Secret 通过 appSecretRef 引用,默认读环境变量 FEISHU_APP_SECRET,再读 dsh 凭据文件
domain: feishu # 国际版 Lark 填 lark
groupRoutes:
- { conversationId: oc_orders, pluginId: business-orders }业务插件用 onPluginMessage(或代码规则 onMessage)接收;conversationId 是群或单聊的 chat_id:
export const inject = ['feishu']
export async function apply(ctx: Context) {
const subscription = ctx.feishu.onPluginMessage('business-orders', async (message, { signal }) => {
await handleOrder(message.content, { signal, eventId: message.eventId })
})
await subscription.ready
}发送:await ctx.feishu.send({ markdown: '## 标题\n正文', target: { chatId: 'oc_xxx' } });text 发纯文本,markdown 以交互卡片发送,target 也可以是 { openId }。
消息 FeishuAtMessage 在 ChannelAtMessage 的基础上增加:
字段 · 说明 chatType · 'p2p'(私聊)或 'group'(群聊) messageType · 飞书原始消息类型,如 text、post、image;text 消息的 content 去掉所有 @ 机器人自己的地方(不只开头那个)、其他 @ 还原为 @名字,@_all(@所有人)原样保留;post 只提取 text / a 节点文字(有标题时为第一行,不含 @);其他类型为空串 mentions · 消息中被 @ 的对象:Array
senderId 是发送者的 open_id,eventId 取飞书 message_id。错误为 FeishuSendError,code 为 invalid_target、auth_failed、permission_denied、rate_limited、timeout、aborted、network、api_error;其中 rate_limited、timeout、network 可重试,api_error 在 HTTP 5xx 时可重试,aborted(调用方中止或 Service 卸载)不可重试。
注意:
searchRecipients('group', …) 返回机器人所在的群以及机器人可见的公开群,个人请直接填写 open_id。选中机器人不在的群时,发送会报 permission_denied(230002)。@larksuiteoapi/node-sdk,开通的权限见「环境要求」。未命中路由的消息同样保留最近记录,可在设置页飞书卡片查看或调用 ctx.feishu.unmatchedMessages()。ctx.wecom 以企业微信智能机器人身份发送 Markdown 消息,并通过长连接接收群内 @ 机器人与单聊机器人的消息。在 Profile 的 cordis.patch.yml 中启用 agent-kit-wecom:
- id: agent-kit-wecom
disabled: false
config:
botId: aibxxxx # Secret 通过 secretRef 引用,默认读环境变量 WECOM_BOT_SECRET,再读 dsh 凭据文件
groupRoutes:
- { conversationId: wrk_orders, pluginId: business-orders }业务插件用 onPluginMessage(或代码规则 onMessage)接收,并用 reply 直接回复;conversationId 群聊为 chatid,单聊为对方的 userid:
export const inject = ['wecom']
export async function apply(ctx: Context) {
const subscription = ctx.wecom.onPluginMessage('business-orders', async (message, { signal, reply }) => {
const result = await handleOrder(message.content, { signal, eventId: message.eventId })
await reply({ markdown: `已处理:${result}` })
})
await subscription.ready
}发送:await ctx.wecom.send({ markdown: '## 标题\n正文', target: { chatId: 'wrk_xxx' } });text 与 markdown 都以 Markdown 发送,target 也可以是 { userId }。at 为 { userIds?, all? },渲染为正文末尾的 ` / ;智能机器人的 Markdown 是否支持这种提及尚未核实(官方文档只在 webhook 的 text / markdown 中给出 ,@all` 只用于 text),可能只显示为普通文字,待真机走查确认。
消息 WecomAtMessage 在 ChannelAtMessage 的基础上增加:
字段 · 说明 chatType · 'group'(群聊)或 'single'(单聊) messageType · 企业微信原始 msgtype;text 取文字(群消息去掉开头的 @ 机器人,规则为尽力而为,尚未用真实消息验证),mixed 拼接其中的文字,voice 取转写文字,其他类型为空串 quote · 被引用消息的文字(有则带)
senderId 是发送者的 userid,eventId 取企业微信 msgid。错误为 WecomSendError,code 为 invalid_target、auth_failed、not_interacted、rate_limited、not_connected、timeout、aborted、api_error;其中 rate_limited、not_connected、timeout 可重试,其余不可重试。
注意:
not_interacted,先在群里 @ 机器人一次即可;每会话每分钟 30 条、每小时 1000 条,超出为 rate_limited。searchRecipients 只能在这些会话里查,名称就是会话 ID。idempotencyKey 只在本进程内去重,发送失败不会自动重试;timeout 不代表未送达。SDK 的消息回执超时固定为 5 秒,单次发送实际最多约等 5 秒。reply() 与 send() 一样要求 text / markdown 二选一,但忽略 idempotencyKey。failed;网络错误与单次认证失败时 SDK 自动重连,状态为重连中(degraded),不拒绝等待中的订阅。dryRun: true 只记日志不连接,但仍需安装 SDK,且 Secret 必须能解析。failed,需要重载该行。userid 为加密 ID,不要假设它的格式。@wecom/aibot-node-sdk,开通方式见「环境要求」。未命中路由的消息同样保留最近记录,可在设置页企业微信卡片查看或调用 ctx.wecom.unmatchedMessages()。没有 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-feishu、agent-kit-wecom、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-feishu
disabled: false
config:
appId: cli_xxxx # App Secret 不写在这里:设置环境变量 FEISHU_APP_SECRET
domain: feishu # 国际版 Lark 填 lark
groupRoutes:
- { conversationId: oc_orders, pluginId: business-orders }
- id: agent-kit-wecom
disabled: false
config:
botId: aibxxxx # Secret 不写在这里:设置环境变量 WECOM_BOT_SECRET
groupRoutes:
- { conversationId: wrk_orders, pluginId: business-orders }
- id: agent-kit-notify
disabled: false
config:
channel: dingtalk # 默认渠道:dingtalk、feishu 或 wecom;需要启用对应渠道的行
- 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 时整段替换;未给出的字段使用默认值。