首页
/ Multica 插件实战:从 Deploy Sentinel 示例看生产事故响应插件的完整实现

Multica 插件实战:从 Deploy Sentinel 示例看生产事故响应插件的完整实现

2026-09-05 21:49:57作者:幸俭卉

本文以 examples/plugins/deploy-sentinel 目录下的生产事故响应插件为蓝本,完整拆解一个"真实团队会怎么写"的 Multica 插件:四类触发器(agent / manual / ui / event)与两种传输(http / mcp)如何在一份清单中协作,钩子后端如何做签名验签与业务拒绝,以及技能(SKILL.md)与钩子如何配合训练 Agent 的事故处理流程。读完后你应能独立发布并安装一个带签名校验、回滚拒绝规则和 MCP 工具采纳的 Multica 插件。

Deploy Sentinel 解决的是真实运维场景:当一个"事故 issue"出现时,把最近的相关部署关联起来、让 Agent 按团队自己的规则提交回滚申请、在事故创建时呼叫值班工程师,并采纳团队已有的 metrics MCP 服务器。它存在于仓库中,正是因为同一目录下的其他示例各自只演示一件事,而它演示的是一个完整插件应有的形状——README 原话概括为"four triggers, both transports, real business rules that refuse things, and a skill that teaches an agent when to use any of it"。

插件贡献清单:六个组成部分各自演示什么

清单文件 multica.plugin.json 声明了六项贡献,每一项都对应一种插件能力或一种设计要点:

贡献 类型 演示的要点
incident-response skill 资源 一份 SKILL.md 成为工作区里的普通技能;卸载插件时一并移除
correlate_deploys hook,agent + manual + ui 触发 一个钩子、三个调用方:Agent 拿到工具,人拿到按钮,面板拿到函数
request_rollback hook,agent + manual 触发 一个会拒绝的写操作:部署太旧、理由太短都拒绝
page_oncall hook,event 触发 issue.created / issue.status_changed 事件上触发,永不阻塞任何东西
metrics hook,mcp 传输 采纳外部 MCP 服务器的工具,须经管理员逐一审批
incident issue_panel 表面 人看的一面,运行在无凭证的沙箱 iframe 中

对应的清单结构是:

  • contributes.surfacesincident 表面,类型 issue_panel,入口 ui/main.js,平台限定 ["web", "desktop"]
  • contributes.hooks:上面列出的四个钩子,各自声明 triggersinput_schematransporttimeout_ms
  • contributes.resources:技能资源 incident-response,入口指向 skills/incident-response/SKILL.md

权限与配置:清单里声明了什么

清单的 scopes 数组声明插件需要的全部权限:

"scopes": [
  "issues:read",
  "issues:write",
  "comments:read",
  "comments:write",
  "storage:workspace",
  "net:sentinel.example.com",
  "net:metrics.example.com"
]

前五项是平台内能力(读写 issue 与评论、工作区存储),后两项 net: 范围是"钩子可以外联哪些主机"的白名单——net: 指向精确的主机名而非 URL,sentinel.example.commetrics.example.com 必须分开声明。这也是插件安装审批时管理员看到的授权范围,钩子永远无法触达管理员未批准的主机。

config 段定义安装后由管理员填写的表单字段,六种类型各有一例:

配置项 类型 说明 必填
service_prefix string 服务名前缀,关联部署时拼在用户输入前
rollback_window_minutes number 部署必须多"新鲜"才有资格回滚
require_incident_label bool 只关联打了 incident 标签的 issue
environment enum 环境,取值 production / staging
sentinel_token secret Deploy Sentinel API 令牌
metrics_credential secret metrics MCP 服务器令牌

注意两个 secret 字段:manifest 声明两个机密配置项,意味着安装存储配置时需要一套加密存储(仓库的集成测试里正是为此注入了 secretbox,见 plugin_example_test.go)。

四个钩子的声明要点:

  • correlate_deploystriggers: ["agent", "manual", "ui"],传输 http,指向 https://sentinel.example.com/hooks/correlate,超时 15 秒。输入 schema 要求 service,可选 window_minutes(默认 120)。工具描述里特意写明"空列表本身也是有用的答案",Agent 读到这段描述会改变它的行为。
  • request_rollbacktriggers: ["agent", "manual"],超时 20 秒。描述里直接写明它不执行回滚、只提交变更申请,且会拒绝超出时间窗的部署、要求书面理由。
  • page_oncalltriggers: ["event"]events: ["issue.created", "issue.status_changed"],超时 10 秒。没有输入 schema——它是被事件驱动的。
  • metricstriggers: ["agent"],传输 {"type": "mcp", "url": "https://metrics.example.com/mcp"},超时 30 秒。它不声明工具,工具由远端 MCP 服务器提供。

值得照抄的三个设计决策

README 用整节篇幅强调这个示例"值得抄的部分",逐条对应到源码:

1. request_rollback 会说"不"

handler.mjs 中,requestRollback 有三道拒绝:

  1. 部署 ID 不存在 → rejected: Unknown deploy ...
  2. 理由长度不足 20 字符 → 拒绝文本是 "A rollback request needs a written reason (at least 20 characters) describing the evidence, not the conclusion.";
  3. 部署年龄超过 rollback_window_minutes 配置 → 拒绝文本解释"超过这个点,向前修复通常更安全,且该由人决定"。

只有三道全过才返回 status: "filed" 和一个变更 ID,且响应里附带 next_step: "A human must approve this change request. Nothing has been rolled back yet.",防止 Agent 把"申请已提交"误报成"事故已处理"。

README 的观点是:只成功的钩子处理器是简单情形,会拒绝的才有用。被迫在提交变更申请前写出证据的 Agent 会产生质量更高的申请——而且拒绝文本会解释原因,Agent 可以据此行动,而不是盲目重试。

2. correlate_deploys 把"没有变化"当答案报告,而不是返回空列表

correlateDeploys 的返回体永远包含 summary 字段:窗口内没有部署时,summary 是 "No deploys to X in the last N minutes. This is unlikely to be a deploy.",而不是把"空数组"留给 Agent 自己推断。技能侧(见下文)则明确告诉 Agent:空结果意味着应该停止在部署方向上找原因。README 的对比很精确——返回 [] 的工具和明确说"这个窗口没有部署、这大概率不是部署问题"的工具,书写成本相同,在 Agent 手里的行为截然不同。

3. 技能与钩子是配套设计的

SKILL.md 按名字点出这些工具并规定使用顺序:先确定影响面,再确定"开始时间"(第一个时间戳通常是有人注意到而非发生的时刻),然后才调 correlate_deploys,窗口起点必须早于第二步得到的时间戳。它还规定了回滚理由的写法——"写证据,不写结论",并给了好/坏示例("error rate on checkout-api went 0.2% → 6% within 90s of deploy d-4821..." vs "this deploy broke checkout")。README 的结论是:两半单独都不如合在一起好用。

钩子后端:一个真实 hook server 必须做对的事

handler.mjs 是插件作者自己运行的服务器("Multica never runs this — the plugin author does"),文件头注释直接点明这里集中了"一个真实钩子后端必须做对、且别处找不到"的四件事:验签、拒重放、应用团队规则、用单次调用凭证回写 issue。

签名与重放防护

Multica 每次外发钩子调用都携带 x-multica-signaturex-multica-timestamp 头,签名算法是对 timestamp + "." + rawBody 做 HMAC-SHA256。服务端校验流程(verifySignature):

  1. 先查时间戳年龄,超出 REPLAY_WINDOW_SECONDS = 300 秒直接拒绝——源码注释解释:Multica 特意把 timestamp 纳入签名,正是为了让一个"旧的、签名有效的请求"无法稍后被重放;
  2. 计算期望签名后,先比较长度再用 timingSafeEqual 做恒定时间比较——因为 timingSafeEqual 在长度不等时会抛异常而非返回 false,而长度本身不是机密,可以先查。

签名密钥来自 MULTICA_SIGNING_SECRET 环境变量,即在工作区设置中签发插件令牌时一次性显示的 whsec_... 值;未配置时每个请求都会被拒。

单次调用的回写凭证

Multica 在请求体中随附 callback_urlcallback_token。该令牌只限于本次调用,钩子一返回即被吊销——所以处理器想写的东西必须在回复之前写完。commentOnIssue 因此是被 await 的而非 fire-and-forget;同时回写失败只打警告不使钩子失败,源码注释解释:Agent 仍然拿到了它的答复,"一条缺失的评论可以补救,一个失败的工具调用则不行"。

还有一个细节体现了触发方差异:correlate_deploystriggeruimanual 时才会把结果以评论形式写回 issue(人在面板里点击,答案留在同事能看见的地方),而 agent 调用不写评论——Agent 自己会把发现写进 issue。

事件钩子:快速确认,别阻塞

page_oncall 分支只打印并立即 202 Acknowledged。源码注释说明原因:事件钩子的响应会被丢弃,返回没人读的东西没有意义,在这里阻塞只会白白占住一个 Multica worker。

MCP 传输:采纳外部服务器的工具,审批与 schema 钉住

metrics-mcp.mjsmetrics 钩子背后的 MCP 服务器,存在的意义就是演示 mcp 传输的适用场景:团队已经跑着一个 MCP 服务器,不想把它重新包装成 HTTP 钩子。它用 Bearer token(METRICS_TOKEN 环境变量,对应插件配置的 metrics_credential 机密字段)做鉴权,实现 JSON-RPC 的 initialize / notifications/initialized / tools/list / tools/call 四个方法,提供三个工具:

  • query_timeseries:查询时间窗内的指标序列;
  • list_alerts:窗口内触发的告警,按时间倒序,附阈值;
  • service_dependencies:该服务调用了谁、被谁调用,用于判断错误是源自本服务还是来自上游。

http 钩子的关键区别在审批模型上:在管理员打开 metrics 审批面板并勾选之前,没有任何 metrics 下的工具可以被调用——README 称"这是 mcp 传输与 http 钩子之间全部的差异"。README 的"Try breaking it"一节给出两个破坏性实验:往 metrics-mcp.mjs 里加一个新工具再重开审批面板,新工具以未批准状态出现,勾之前 Agent 调不到;修改已批准工具的 inputSchema 并重启,Agent 任务能正常开始,但该连接会被拒绝,因为 schema 摘要(digest)与当初批准的不再匹配。也就是说,批准是"按 schema 摘要钉住"的,工具改了形状等于未批准。

issue 面板表面:沙箱 iframe 里的行为脚本

ui/main.jsincident 表面的入口。文件头注释与 plugin-sdk 文档 共同说明了表面的运行约束:

  • 表面是单个脚本、无模块图:Multica 存储发布的制品并在一个生成的文档中提供服务,没有可供静态 import 解析的模块图。真实插件会把 @multica/plugin-sdk 打进这个单文件;示例则内联了几个所需调用,保持可读且零安装。
  • iframe 以 sandbox="allow-scripts" 挂载且不带 allow-same-origin,即不透明源:没有宿主 cookie、没有宿主存储、没有任何凭证。
  • 页面与宿主的全部通信走桥(bridge):main.jsglobalThis.__multicaPluginBridgePortV2 取到宿主注入的 MessagePort,所有 API 调用(/context/issues/.../storage/workspace/.../hooks/...)都是发给宿主的消息,宿主以登录用户自己的会话重新发起请求,并检查插件被授予的范围。net: 范围决定 CSP 的 connect-src;未声明任何 net: 范围时,表面连发不出一个网络请求。

行为上,correlate() 先读工作区存储里的 default_service(服务名是工作区级设置而非逐 issue 手填,保证同一服务的所有调查者得到同一答案),再经 ui 触发器调用自家的 correlate_deploys 钩子——宿主对出站调用签名并施加限流,"frame 永远看不到端点"。面板上每个部署卡片带一个 "Request rollback" 按钮,点击后 prompt 要求填写理由("审批人会读它——写证据,不写结论"),再经 manual 语义调用 request_rollback。错误处理也有讲究:钩子调用失败时明确显示"Deploy Sentinel could not reach its backend",而不是渲染一个空面板让人误读为"没有部署"。

本地跑起来:完整流程

本地需要两个服务器,都是插件作者自己的;Multica 从不运行它们。两者都必须提供 HTTPS——因为钩子的传输 URL 必须是 https:// URL,否则清单装不上。

第一步:生成一次性开发证书

openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
  -keyout dev-key.pem -out dev-cert.pem \
  -subj "/CN=127.0.0.1" -addext "subjectAltName=IP:127.0.0.1"

第二步:启动两个服务器

# 钩子端点。签名密钥就是签发插件令牌时一次性显示的 whsec_…
MULTICA_SIGNING_SECRET=whsec_... node server/handler.mjs      # :8788

# `metrics` 钩子背后的 MCP 服务器。
METRICS_TOKEN=metrics-dev-token node server/metrics-mcp.mjs   # :8789

两个服务器都读取 TLS_CERT / TLS_KEY,缺省为工作目录下的 dev-cert.pem / dev-key.pemhandler.mjs 读不到证书文件时会退出并打印 openssl 命令提示。端口可用 PORT 覆盖,缺省 8788(钩子)与 8789(MCP)。

第三步:让 Multica 指向它们

两个端点都在回环地址上,而出站防护(outbound guard)按设计拒绝回环,所以要用开发开关显式声明:

export MULTICA_PLUGIN_DIR=examples/plugins
export MULTICA_PLUGIN_DEV_ORIGINS=https://127.0.0.1:8788,https://127.0.0.1:8789
export MULTICA_PLUGIN_DEV_CA=/path/to/your-dev-ca.pem

README 对这三个开关的边界界定得很清楚,值得记住:

  • MULTICA_PLUGIN_DIR 只是前端制品的发布快捷方式——上面两个服务器仍然是你自己要跑的,net: 范围仍然是触达它们的授权依据;
  • MULTICA_PLUGIN_DEV_ORIGINS 决定"地址是否必须公网",MULTICA_PLUGIN_DEV_CA 决定"信任哪张证书";
  • 它们从不关闭证书校验,也从不放宽 net: 范围——钩子仍然够不到管理员没有批准的主机。本地也必须 HTTPS,这是清单校验器要求的;MULTICA_PLUGIN_DEV_CA 的作用是让自签名证书被信任。

第四步:发布、安装、审批

MULTICA_PLUGIN_DIRdeploy-sentinel 发布,安装出现的版本,填写配置表单,然后打开 metrics 审批面板勾选你希望 Agent 能触及的工具——勾选之前 metrics 下没有任何工具可被调用。

handler.mjs 内置了固定的演示部署数据(d-4821 4 分钟前、d-4820 51 分钟前、d-4816 92 分钟前、d-4802 380 分钟前),保证 README 的推演可复现。四个破坏性验证场景:

  • 让 Agent 回滚 d-4802:它已经 380 分钟了,插件拒绝并解释原因;
  • 用理由 "broken" 请求回滚:太短,被拒;
  • metrics-mcp.mjs 加工具再重开审批面板:新工具以未批准出现,勾选前 Agent 调不到;
  • 修改已批准工具的 inputSchema 并重启:schema 摘要不再匹配批准记录,连接被拒。

测试:安装的是这份真实清单

plugin_example_test.go 是这个示例的守门人。测试注释解释了自己为什么这么写:它安装的是仓库里这份真实清单而不是旁边手写的一份 fixture——"手写的 fixture 会在示例腐烂后继续通过,而示例才是插件作者要抄的那个文件。如果有人弄坏了清单,这个测试会说出来。"

具体流程是:把 examples/plugins/deploy-sentinel 拷贝到临时目录作为 MULTICA_PLUGIN_DIR,只改写两处——把两个端点主机指向测试服务器、把对应 net: 范围指向测试主机(清单里的每个工具描述、每个字段原样保留);然后走真实的发布与安装路径。断言覆盖:技能落入技能表、Agent 工具列表恰好包含声明了 agent 触发的钩子、一次 Agent 钩子调用带签名出去并按签名校验回来、metrics 工具被发现、批准前被拒、批准后按 schema 摘要钉住。安装前测试还为插件注入了 DeploymentKey——注释说"签名是让钩子调用可被插件自己的服务器验证的手段;没有部署密钥,引擎干脆拒绝外呼"。

README 最后一段解释了两套文件的分工:.mjs 文件是你手工运行的真实后端(需要 Node),测试用的是 Go 写成的、应答同一套契约的服务器,所以 CI 不需要 Node 环境。

小结:从示例到你自己插件的抄写清单

Deploy Sentinel 的价值在于它把一个插件作者容易省略的部分都摆了出来,且每一处都有源码对应:

  1. 清单scopes(含 net: 主机白名单)+ config 表单(六种字段类型)+ contributes(surfaces / hooks / resources 三类),见 multica.plugin.json
  2. 钩子后端:HMAC-SHA256 验签、300 秒重放窗口、恒定时间比较、单次调用回写令牌、按触发方决定是否回写评论、事件钩子快速 ACK,见 handler.mjs
  3. 会拒绝的写操作:拒绝理由写进响应文本,让 Agent 能据此修正行为而不是盲目重试;
  4. 把"无变化"当答案summary 字段显式陈述空结果的含义,并在 SKILL.md 中规定 Agent 收到该含义后的行动;
  5. MCP 采纳:外部服务器提供工具,管理员按 schema 摘要逐一审批,见 metrics-mcp.mjs
  6. 沙箱表面:单脚本、无模块图、不透明源 iframe,一切经桥以用户会话重放,见 ui/main.jsplugin-sdk 文档
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384