fix(agents): 四家桥的 read_inbox 一律按会话收窄(dsh/opencode/zcode/homeagent)
用户:「你还是没修好不同 session agent 收件箱隔离的问题」。上一轮我只修了 **pi**, 另外四家还漏着 —— 它们是**每一家各自实现** read_inbox,不修就还是漏。 ## 缺陷 列表按 Agent 列(整个收件箱),而 read_inbox 按契约把**列出来的都标成已读** ⇒ A 会话的回合会把 B 会话的未读标掉 ⇒ B 之后按 `?status=unread` 补投时 再也看不到那封信(静默丢信,不是"少看一封")。用户是在别的 Agent 上看到它的。 ## 四家的修法(各自平台能力不同,但都要"并发安全") | 桥 | 会话来源 | 为什么这样做 | |---|---|---| | dsh | 工具第二参数 `exec.agent.id` → `reverseMap` | 平台就在上下文里给了会话;**不能用模块级"当前会话"变量**(同进程可能同时跑多条会话的回合,会互相覆盖) | | opencode | 工具第二参数 `context.sessionID` → `reverseMap` | 同上 | | zcode | `AGENTMAIL_SESSION_ID`(在**调用时**读) | 一轮一个进程,驱动本来就注入它给授权钩子用;调用时读,避免将来复用进程拿到旧值 | | homeagent | `p.currentSessionID`(回合开始设、结束清) | Go 插件,本来就有这个状态 | 取不到会话一律**退回整体收件箱**(历史行为),不猜 —— 猜错就是静默丢信。 ## 判据 - 服务端语义:`server/internal/repo/session_scope_test.go`(读 A 不动 B、列表收窄、 计数与列表口径一致)。 - 桥侧接线:dsh 4 条、opencode 3 条、zcode 3 条、homeagent Go 1 条 (`TestInboxURLScopedBySession`,直接断言拼出来的 URL)。 每家都带**判据自检**:拿旧写法喂进来必须判红;dsh/opencode 还专门断言 "不得用模块级当前会话变量"。 - **部署件**(不是仓库):四家的部署快照里都能 grep 到 `session_id=`。 - **线上实测**:用 opencode 自己的 Agent 身份请求收窄列表 —— 会话 A 3 封、 会话 B 0 封、两者无交集、且都是全量的子集。 套件:opencode **331**、dsh **381**、zcode **385**、homeagent ok,全绿。 四家桥已重新部署(dsh/opencode/zcode 快照切换 + homeagent 新 plugin.bin 并重启), 四个服务均 active。
This commit is contained in:
@ -822,6 +822,18 @@ export function apply(ctx: any, config: PluginConfig): void {
|
||||
* 推不出来,重启后确实无法定位热更新 —— 已知取舍;下次投递时会按邮件里
|
||||
* 带的 permission_mode 重新 apply,档位不会丢。
|
||||
*/
|
||||
/**
|
||||
* 从工具运行上下文里取"这次调用属于哪条**邮件**会话"。
|
||||
*
|
||||
* 取不到返回空串(= 退回整个 Agent 的收件箱)。宁可退回旧行为,也不猜 ——
|
||||
* 猜错会把别人会话的未读标掉,那是静默丢信。
|
||||
*/
|
||||
function mailSessionOf(exec: any): string {
|
||||
const dshSessionId = String(exec?.agent?.id ?? '');
|
||||
if (!dshSessionId) return '';
|
||||
return reverseMap.get(dshSessionId) ?? '';
|
||||
}
|
||||
|
||||
function findLiveDshSession(mailSessionID: string): { id: string; agent: any } | undefined {
|
||||
const bound = sessionMap.peek(mailSessionID);
|
||||
if (bound) {
|
||||
@ -1256,10 +1268,20 @@ export function apply(ctx: any, config: PluginConfig): void {
|
||||
schema: { type: 'string' },
|
||||
render: (_args: any, value: string) => [{ type: 'text', text: value }],
|
||||
},
|
||||
async execute(args: any): Promise<string> {
|
||||
// ★ 第二个参数是平台给的运行上下文:`exec.agent` 就是这次调用所属的 DSH 会话。
|
||||
// 桥里有 reverseMap(DSH 会话 → 邮件会话),因此可以做到**并发安全**的收窄 ——
|
||||
// 不能用模块级"当前会话"变量(同一进程里可能同时有多个会话的回合在跑,
|
||||
// 那个变量会被互相覆盖)。
|
||||
//
|
||||
// 缺陷(用户报的):「不同 session 的 agent 都可以看到全部邮件」:
|
||||
// 列表按 Agent 列且 read_inbox 会把列出的都标已读 ⇒ A 会话标掉 B 会话的未读
|
||||
// ⇒ B 之后按 ?status=unread 补投时再也看不到那封信(静默丢信)。
|
||||
async execute(args: any, exec?: any): Promise<string> {
|
||||
const status = args.status || DEFAULT_INBOX_STATUS;
|
||||
const mailSessionID = mailSessionOf(exec);
|
||||
const scope = mailSessionID ? `&session_id=${encodeURIComponent(mailSessionID)}` : '';
|
||||
const { mails } = await client.get(
|
||||
`/mail/inbox?status=${status}&limit=${args.limit || DEFAULT_INBOX_LIMIT}`
|
||||
`/mail/inbox?status=${status}&limit=${args.limit || DEFAULT_INBOX_LIMIT}${scope}`
|
||||
);
|
||||
|
||||
// 渲染与已读策略放 lib/inbox-format.js:它们与平台 SDK 无关,
|
||||
|
||||
Reference in New Issue
Block a user