Files
MailUI4Agents/deploy/check-shared-libs.sh
JianFeeeee e0e6f86d94 feat(zcode): AgentMail 的 ZCode 插件 —— MCP 工具面 + 官方宿主启动验证
ZCode 用插件扩展能力(.zcode-plugin/plugin.json 声明 skills/commands/hooks/
mcpServers),所以适配它的正确形状是**插件**而不是又一个独立桥进程。

本提交是第一步:把 AgentMail 的工具面做成 MCP 服务器。

协议层(lib/mcp-rpc.mjs)手写,不引 @modelcontextprotocol/sdk:
协议面只有 initialize / notifications/initialized / tools/list / tools/call,
手写可省掉一条构建链与 1MB 打包产物(与 pi/opencode/dsh 三桥零运行时依赖的
取向一致),并让这一层成为可穷举的纯函数。分帧照官方插件产物实测确认是
换行分隔 JSON(Content-Length 出现 0 次,StdioServerTransport + split("\n"))。

工具面(lib/tools.mjs)与另三个桥**同名同参**,渲染走共用的
addressing/inbox-format/discovery(逐字节同源,已纳入 check-shared-libs.sh)。
测试里有一条断言直接拿 pi 桥的工具名做对照:少一个就让某平台行为与其它平台不同,
那种问题只在单平台复现,排查代价最高。

两处按真实缺陷定的行为:
- 工具失败回 result+isError 而非 JSON-RPC error —— 后者会让模型看不到失败原因,
  只能重试(opencode 连试 6 次发不出附件正是这个后果)
- attachment_ids 声明放宽为 anyOf 数组/字符串并在桥侧归一 —— 模型常写成
  JSON 字符串,服务端严格解码会拒(同样来自 opencode 那次失败)

入口 mcp/server.mjs 修掉一个真实缺陷:stdin 关闭即 process.exit 会杀掉在途请求,
表现为「协议全对但访问网关的调用完全没有响应」。现按在途计数 drain,
且把 stdout 写入也计入,避免最后一条响应卡在缓冲区。

顺带修 check-shared-libs.sh 的一个既有假绿:本机 PATH 上的 diff 是鸿蒙 SDK
工具链的 diff,不认 -q 且对不同的文件仍返回 0 —— 于是该检查器**一直是永真输出**。
改用 cmp -s,并加自检(判据本身必须先被证明能发现差异)。反向验证:
让 zcode 或 pi 的共用模块分叉,检查器都正确报错并返回 1。

验证:
- 单元 33 项 + 继承共用测试 87 项 = 120/120
- `zcode plugins list` → agentmail@inline [enabled],mcp: plugin:agentmail:agentmail
- 经官方 `node zcode.cjs __zcode-plugin-host <server.mjs>` 启动 → 握手与 tools/list 正常
- 真实网关调用:以 zcode 身份 read_inbox / suggest_address / list_contacts 均返回
2026-09-12 13:47:51 +08:00

113 lines
4.6 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# 共用模块必须逐字节相同 —— 见 docs/PLUGIN-CONTRACT.md 第六节。
#
# 一侧改了另一侧没改,几个平台的行为就会悄悄分叉:同一封邮件在 opencode 那边
# 标了已读、在 DSH 那边没标,而两处代码看起来都"对"。
#
# 比对一律以 opencode 为基准逐个对比,而不是两两对比:后者在三方都不同时
# 会打出三条互相矛盾的差异,读的人无从判断谁是对的。
#
# # 为什么每个 peer 的清单不同
#
# 四个插件共用的是**协议无关**的那几个模块(地址解析、收件箱渲染、附件 id 归一…),
# 但各自用到的子集不同:
#
# - pi / dsh / opencode 是邮件驱动的**桥**需要会话快照、中继去重、SSE 客户端等;
# - zcode-mail-bridge 目前是 **MCP 工具服务器**(模型侧的工具面),
# 不订阅 SSE、不管会话生命周期所以只需要那 5 个协议无关模块。
#
# 用「对等清单」会逼它拷一份用不到的 relay/session 模块进来 —— 那些文件
# 永远不会被执行,却要跟着一起同步,反而让「哪些是真的共用」变得看不清。
# 于是改成显式列举:**声明共用就必须逐字节相同**,声明之外的模块不检查
# (因为根本没拷过来,结构上就不可能分叉)。
set -euo pipefail
BASE=plugins/opencode-mail-bridge
PEERS=(plugins/dsh-mail-bridge plugins/pi-mail-bridge plugins/zcode-mail-bridge)
ALL_LIBS="relay-dedup relay-policy relay-key permission-mode bounded inbox-format session-snapshot workspace model-scope catchup addressing discovery rename-proposal permission-grants adopt sse-client user-question attachment-ids"
ALL_TESTS="relay-policy relay-key permission-mode bounded inbox-format session-snapshot workspace model-scope catchup addressing discovery rename-proposal permission-grants adopt sse-client user-question attachment-ids"
# MCP 工具服务器用到的协议无关子集(实现与测试都同步)。
MCP_LIBS="addressing inbox-format bounded discovery attachment-ids"
MCP_TESTS="addressing inbox-format bounded discovery attachment-ids"
# ── 判据:用 cmp不用 diff ─────────────────────────────────────────
#
# 本机 PATH 上的 `diff` **不是 GNU diff**,而是鸿蒙 SDK 工具链里的
# /opt/huawei/harmonyos/ohos-sdk/linux/toolchains/diff。它不认 `-q`
# 却对**内容不同的文件照样返回 0** —— 于是 `if ! diff -q ... >/dev/null`
# 恒为假,整支脚本变成永真输出(本检查器曾长期如此,实测才发现)。
# cmp 的语义明确:相同 0、不同 1。
same() { cmp -s "$1" "$2"; }
# 判据本身必须先被验证。否则「比较永远说相同」与「真的都相同」
# 在结果上完全一样,而这正是上面那个坑的形状。
selfcheck() {
local a b
a=$(mktemp) && b=$(mktemp)
printf 'x\n' >"$a"
printf 'y\n' >"$b"
if same "$a" "$b"; then
echo "检查器自检失败:比较函数无法发现差异(别用 PATH 上的 diff" >&2
exit 1
fi
printf 'y\n' >"$a"
if ! same "$a" "$b"; then
echo "检查器自检失败:相同内容的两个文件被判为不同" >&2
exit 1
fi
rm -f "$a" "$b"
}
selfcheck
libs_for() {
case "$1" in
*zcode-mail-bridge) echo "$MCP_LIBS" ;;
*) echo "$ALL_LIBS" ;;
esac
}
tests_for() {
case "$1" in
*zcode-mail-bridge) echo "$MCP_TESTS" ;;
*) echo "$ALL_TESTS" ;;
esac
}
# 差异明细也走 cmp不借 diff。
show_diff() {
cmp "$1" "$2" 2>&1 | head -3
}
fail=0
for peer in "${PEERS[@]}"; do
for f in $(libs_for "$peer"); do
if [[ ! -f "$peer/lib/$f.js" ]]; then
echo "共用模块缺失:$peer/lib/$f.js" >&2
fail=1
continue
fi
if ! same "$BASE/lib/$f.js" "$peer/lib/$f.js"; then
echo "共用模块已分叉lib/$f.js$BASE vs $peer" >&2
show_diff "$BASE/lib/$f.js" "$peer/lib/$f.js" >&2
fail=1
fi
done
# 测试同样要同源:共用模块的行为约定写在测试里,
# 只同步实现不同步测试,等于允许一侧偷偷放宽约定。
for f in $(tests_for "$peer"); do
if [[ ! -f "$peer/test/$f.test.mjs" ]]; then
echo "共用测试缺失:$peer/test/$f.test.mjs" >&2
fail=1
continue
fi
if ! same "$BASE/test/$f.test.mjs" "$peer/test/$f.test.mjs"; then
echo "共用测试已分叉test/$f.test.mjs$BASE vs $peer" >&2
fail=1
fi
done
done
[[ $fail -eq 0 ]] && echo " 共用模块四方同源opencode 基准 vs dsh / pi / zcode判据已自检" || exit 1