fix(permission): 另外四家桥的"人类说明"缺口 —— 三家修、一家本来就有
承接 453f451(pi 桥)。用户批准后把同款缺口在其余四家逐一核对:
**zcode 本来就有**(`说明:${decision.note}`),homeagent / dsh / opencode 三家缺。
## homeagent(Go,能完整修)
SSE 事件结构里**根本没有 Note 字段**(json 里只有 decision/decided_by)⇒ 备注在
解码那一步就没了。补上字段,并把提示词抽成纯函数 `permissionDecisionPrompt(evt)`,
加了判据(说明必须出现 + 反向对照:无说明/空白说明不得凭空造出说明段)。
构建(`go build -buildmode=plugin`)后 install 到
`/home/newqqagent/plugins/homeagent-mail-bridge/plugin.bin` 并重启,已核验部署件
含新符号(`grep -a`,中文用 strings 查是查不到的)。
## dsh / opencode(平台回执放不下理由 → 分两步)
两家的审批回执都是**三态字符串**:DSH `ApprovalOutcome` 只有
allowed-once / rejected / cancelled / unavailable,openCode 只有 once / always / reject
—— **没有地方放人类的说明**。所以:
1. 提示词("你之前发起的权限请求已有结论:…")统一走 `permissionPrompt(data)`,
带上 `用户的说明:…`。dsh 原有**三处**内联文案(续谈/新会话/通知投递),
措辞分叉正是这类信息漏掉的地方 —— 判据直接钉"只有一处拼这句话"。
2. 带说明的决策**另投一趟通知**,让模型在会话里看到理由。代价是多一轮;比悄悄
丢掉人的指令轻(原缺陷就是丢了指令,模型把同一条命令换写法又问一遍,连问 9 次)。
3. 决策回执不再被当成"新任务"(内容已随 permission_decision 交付),并记下
`decision_mail_id` 防重复 —— 与 pi 桥同源。
判据:dsh / opencode 各 5 条(含"拿缺陷时的源码形态喂进来必须判红"的自检)。
## 部署与代价
- dsh → 快照 20260914-081456、opencode → 20260914-081516、homeagent → 新 plugin.bin,
三家的服务 active 且心跳/连接已核。
- 重启 dsh 时它正在"续谈"一封邮件(08:10:45 日志)——事后核对:那一轮**已回完**
(faad0037 的 parent = 4919aa88),没有丢活。
- 套件:dsh 372、opencode 323、homeagent go test ok、zcode 382 全绿。
This commit is contained in:
@ -843,6 +843,21 @@ let relaySummaryRef = async () => null;
|
||||
//
|
||||
// relay_key(= opencode 的 permission.id)由服务端随决策事件回传,
|
||||
// 所以插件重启丢了 pendingPermissions 也能续上 —— 这个映射不能只存在内存里。
|
||||
/**
|
||||
* 权限结论的提示词 —— **必须带上人类的说明**。
|
||||
*
|
||||
* 2026-09-13 线上缺陷:人类写「我说了让你拉取仓库到program下你听不懂吗」,而桥只给
|
||||
* 模型 `决策` 一个词(`note` 在 SSE 回包里没人读),模型把同一条命令又问了一遍。
|
||||
*/
|
||||
function permissionPrompt(data) {
|
||||
const note = typeof data?.note === "string" ? data.note.trim() : "";
|
||||
return (
|
||||
`你之前发起的权限请求已有结论:${data?.decision}(决策人:${data?.decided_by || "用户"})。` +
|
||||
(note ? `\n用户的说明:${note}` : "") +
|
||||
`\n请据此继续后续工作。`
|
||||
);
|
||||
}
|
||||
|
||||
async function replyPermission(client, directory, data) {
|
||||
const permID = data.relay_key || "";
|
||||
if (!permID) {
|
||||
@ -876,6 +891,13 @@ async function replyPermission(client, directory, data) {
|
||||
body: { response },
|
||||
});
|
||||
pendingPermissions.delete(permID);
|
||||
// openCode 的权限回执只有 once/always/reject,**放不下人类的说明**;而说明是模型
|
||||
// 下一步该怎么改的唯一依据,所以有它时另外投一趟通知(多一轮 < 悄悄丢掉人的指令)。
|
||||
if (typeof data.note === "string" && data.note.trim()) {
|
||||
await deliverMail(client, directory, data, "permission").catch((e) =>
|
||||
console.error("[mail-bridge] 决策说明投递失败:", e?.message || e),
|
||||
);
|
||||
}
|
||||
console.error(`[mail-bridge] 权限 ${permID} -> ${response}(决策人 ${data.decided_by || "?"})`);
|
||||
return { sessionID };
|
||||
}
|
||||
@ -910,7 +932,7 @@ async function deliverMail(client, directory, data, kind) {
|
||||
// 只能在提示词里告知模型档位约束(advisory 路径)。
|
||||
const permBriefing = modeBriefing({ mode: normalizeMode(data.permission_mode), enforcement: 'advisory', workspace: directory || '' });
|
||||
const text = kind === "permission"
|
||||
? `你之前发起的权限请求已有结论:${data.decision}(决策人:${data.decided_by || "用户"})。请据此继续后续工作。`
|
||||
? permissionPrompt(data)
|
||||
: [
|
||||
inboundHeadline({
|
||||
inReplyTo: data.in_reply_to,
|
||||
@ -1207,6 +1229,14 @@ export default async function mailBridge(input) {
|
||||
|
||||
if (type !== "new_mail") return;
|
||||
if (data?.mail_id) deliveredMails.add(data.mail_id);
|
||||
// 决策回执不是"新任务"(内容已随 permission_decision 交付)—— 同 pi 桥那条注释:
|
||||
// 按新邮件再投一次会把人类真正的新邮件挤在这条会话的队列后面。
|
||||
if (data?.mail_type === "permission_decision") {
|
||||
deliverMail(client, directory, data, "permission").catch((e) =>
|
||||
console.error("[mail-bridge] 决策回执投递失败:", e?.message || e),
|
||||
);
|
||||
return;
|
||||
}
|
||||
deliverMail(client, directory, data, "mail")
|
||||
.then(({ sessionID, reused }) => {
|
||||
console.error(`[mail-bridge] ${type} -> ${reused ? "续谈" : "新会话"} ${sessionID}`);
|
||||
|
||||
Reference in New Issue
Block a user