feat: L0 线协议冻结 + 附件链路修复 + 人/Agent 区分
L0 核心: - 严格解码 Decode(DisallowUnknownFields) 全覆盖 29 个 DecodeBody 调用点 - DecodeLenient 心跳专用:容忍新字段但回报 unknown_fields - 400 消息列出本端点接受的全部字段(jsonFieldNames 反射 tag) - 日历 status 校验(create 补字段 + update 拦非法值) - 新增 strictdecode_test.go 10 例 + blob/list_test.go 6 例 A-4 附件挂载回滚:checkAttachable 在 CreateMail 前校验,失败按 解挂→释放 relay→删邮件→退预算回滚,幽灵邮件这条路堵住了 A-5 反向 GC:blob.Store.List() 枚举磁盘(跳 .upload-*), SweepUnreferencedBlobs 按 attachments + calendar_attachments 反查, 48h 年龄下限兜上传窗口。已接进每小时 sweep 循环 C 人/Agent 区分:四个读路径 + threadCols 补 from_human / to_human (EXISTS users 判定),models.Mail 加 ToHuman。前端判据从 workspace 启发式改成显式布尔,mailCounterpart/sessionCounterpart 从 session_workspace 取 path(修 dsh@dsh 拼接 bug) 契约文档:SSE new_mail 补 4 字段(in_reply_to/from_human/ permission_mode/permission_enforcement),B-5 加 B-5.6 (Agent→Agent 不转发),B-3.4 MUST 改条件式,心跳补 mode_enforcement + unknown_fields,demo 死链修复 + from_human 检查 验收清单加 Agent→Agent 负向对照项
This commit is contained in:
@ -12,7 +12,7 @@ PEERS=(plugins/dsh-mail-bridge plugins/pi-mail-bridge)
|
||||
fail=0
|
||||
|
||||
for peer in "${PEERS[@]}"; do
|
||||
for f in relay-dedup relay-policy inbox-format session-snapshot workspace model-scope catchup addressing discovery rename-proposal permission-grants adopt; do
|
||||
for f in relay-dedup relay-policy relay-key permission-mode bounded inbox-format session-snapshot workspace model-scope catchup addressing discovery rename-proposal permission-grants adopt; do
|
||||
if [[ ! -f "$peer/lib/$f.js" ]]; then
|
||||
echo "共用模块缺失:$peer/lib/$f.js" >&2
|
||||
fail=1
|
||||
@ -26,7 +26,7 @@ for peer in "${PEERS[@]}"; do
|
||||
done
|
||||
# 测试同样要同源:共用模块的行为约定写在测试里,
|
||||
# 只同步实现不同步测试,等于允许一侧偷偷放宽约定。
|
||||
for f in relay-policy inbox-format session-snapshot workspace model-scope catchup addressing discovery rename-proposal permission-grants adopt; do
|
||||
for f in relay-policy relay-key permission-mode bounded inbox-format session-snapshot workspace model-scope catchup addressing discovery rename-proposal permission-grants adopt; do
|
||||
if [[ ! -f "$peer/test/$f.test.mjs" ]]; then
|
||||
echo "共用测试缺失:$peer/test/$f.test.mjs" >&2
|
||||
fail=1
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
python3 remote-agent-demo.py [运行秒数]
|
||||
|
||||
它做四件事:注册 → 心跳(带模型目录)→ SSE 长连 → 收到邮件就回一封。
|
||||
真正的插件还要做权限转发、会话命名回写、附件等,见 docs/PLUGIN-GUIDE.md。
|
||||
真正的插件还要做权限转发、会话命名回写、附件等,见 docs/PLUGIN-CONTRACT.md。
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
@ -83,6 +83,13 @@ def reply_to_unread(event):
|
||||
box = api("/mail/inbox?status=unread&limit=5")
|
||||
mails = box.get("mails", box if isinstance(box, list) else [])
|
||||
for m in mails:
|
||||
# B-5.6:收件方是 Agent → 不自动转发。from_human 由服务端判定,
|
||||
# 不依赖插件自己的猜测(早期靠 "from_name == 'human'" 的写法
|
||||
# 在多用户下恒为假,导致回信发给了自己)。
|
||||
if not m.get("from_human", True):
|
||||
print(" 跳过 Agent 来信:", m.get("from_name"), m.get("subject"))
|
||||
api("/mail/read", {"mail_ids": [m.get("mail_id")]})
|
||||
continue
|
||||
print(" 收到:", m.get("subject"), "| 工作目录:", event.get("to_workspace"))
|
||||
api("/mail/send", {
|
||||
"to": m.get("from_name"),
|
||||
|
||||
Reference in New Issue
Block a user