/** * 五个读类端点的请求都要带上**自己那条邮件会话**(opencode)。 * * read_inbox 早就有这一维(缺陷:列表按 Agent 列且按契约标已读 ⇒ A 会话标掉 * B 会话的未读 ⇒ 静默丢信)。服务端现在拿它多干一件事:**由这条会话反查工作区**, * 只有同工作区的会话才放行 —— 一个 Agent 同时服务所有工作区,不收窄时在 TrueAgent * 里干活的 worker 能读到 agentmail 的整条线索(用户 2026-09-14 报的越界)。 * * 服务端语义由 server/internal/repo/workspace_scope_test.go 负责;这里只验接线。 * 两侧都钉:包住了 / 没包住的不存在 —— 只验前者的话,把 withScope 写成恒等函数也能过。 */ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const HERE = dirname(fileURLToPath(import.meta.url)); const src = readFileSync(join(HERE, '..', 'index.js'), 'utf8'); const ENDPOINTS = [ ['read_mail', 'withScope(`/agent/mail/${args.mail_id}`, context)', 'apiGet(`/agent/mail/${args.mail_id}`)'], ['read_thread', 'withScope(`/agent/mail/${args.mail_id}/thread${qs}`, context)', 'apiGet(`/agent/mail/${args.mail_id}/thread${qs}`)'], ['list_contacts', 'withScope("/agent/contacts", context)', 'apiGet("/agent/contacts")'], ['session_participants', 'withScope(`/agent/sessions/${args.session_id}/participants`, context)', 'apiGet(`/agent/sessions/${args.session_id}/participants`)'], ]; for (const [name, scoped, bare] of ENDPOINTS) { test(`★ ${name} 的请求走 withScope(...)`, () => { assert.ok(src.includes(scoped), `${name} 的 URL 没有包在 withScope 里:${scoped}`); assert.ok(!src.includes(bare), `${name} 还有一处没包住的写法:${bare}`); }); } test('★ forward_mail 也带上收窄(它读的是原文)', () => { const scoped = 'withScope(`/mail/${args.mail_id}/forward`, context)'; const bare = 'apiPost(`/mail/${args.mail_id}/forward`, {'; assert.ok(src.includes(scoped), '转发的 URL 没有包在 withScope 里'); assert.ok(!src.includes(bare), '转发还有一处没包住的写法'); }); test('★ suggest_address 的会话候选也带上收窄(它列的是别的会话的别名与标题)', () => { assert.ok(src.includes('qs.set("session_id", mailSessionID)'), 'suggest 没把 session_id 放进查询串'); }); test('withScope 的会话来自平台上下文(并发安全),不是模块级变量', () => { assert.match(src, /function withScope\(path, context\)/, 'withScope 得接住 context'); assert.match(src, /reverseMap\.get\(String\(context\?\.sessionID/, '经 reverseMap 换邮件会话'); assert.ok(src.includes('if (!mailSessionID) return path;'), '拿不到会话就原样返回'); assert.ok(!/let\s+currentMailSessionID/.test(src), '不得用模块级"当前会话"变量'); });