/** * 五个读类端点的请求都要带上**自己那条邮件会话**(zcode)。 * * read_inbox 早就有这一维(缺陷:列表按 Agent 列且按契约标已读 ⇒ A 会话标掉 * B 会话的未读 ⇒ 静默丢信)。服务端现在拿它多干一件事:**由这条会话反查工作区**, * 只有同工作区的会话才放行 —— 一个 Agent 同时服务所有工作区,不收窄时在 TrueAgent * 里干活的 worker 能读到 agentmail 的整条线索(用户 2026-09-14 报的越界)。 * * 服务端语义由 server/internal/repo/workspace_scope_test.go 负责;这里只验接线。 * * 判据两侧都钉:**包住了**(withScope(...) 的整句存在)与**没包住**(去掉包装的 * 那句不存在)。只验前者的话,把 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 tools = readFileSync(join(HERE, '..', 'lib', 'tools.mjs'), 'utf8'); // 每个端点两句话:包住的 / 没包住的。 const ENDPOINTS = [ ['read_mail', 'withScope(`/agent/mail/${encodeURIComponent(id)}?body_limit=0`)', 'client.get(`/agent/mail/${encodeURIComponent(id)}?body_limit=0`)'], ['read_thread', 'withScope(`/agent/mail/${encodeURIComponent(id)}/thread${qs}`)', 'client.get(`/agent/mail/${encodeURIComponent(id)}/thread${qs}`)'], ['suggest_address', 'withScope(`/agent/contacts/suggest?${qs.toString()}`)', 'client.get(`/agent/contacts/suggest?${qs.toString()}`)'], ['list_contacts', 'withScope(`/agent/contacts?limit=${limit}`)', 'client.get(`/agent/contacts?limit=${limit}`)'], ['session_participants', 'withScope(`/agent/sessions/${encodeURIComponent(sid)}/participants`)', 'client.get(`/agent/sessions/${encodeURIComponent(sid)}/participants`)'], ]; for (const [name, scoped, bare] of ENDPOINTS) { test(`★ ${name} 的请求走 withScope(...)`, () => { assert.ok(bare.includes('client.get('), '夹具形状不对:对照组必须是没包住的那句'); assert.ok(tools.includes(scoped), `${name} 的 URL 没有包在 withScope 里:${scoped}`); assert.ok(!tools.includes(bare), `${name} 还有一处没包住的写法:${bare}`); }); } test('★ forward_mail 也带上收窄(它读的是原文)', () => { const scoped = 'withScope(`/mail/${encodeURIComponent(str(a.mail_id))}/forward`)'; const bare = 'client.post(\n `/mail/${encodeURIComponent(str(a.mail_id))}/forward`,'; assert.ok(tools.includes(scoped), '转发的 URL 没有包在 withScope 里'); assert.ok(!tools.includes(bare), '转发还有一处没包住的写法'); }); test('withScope 在调用时读 env,且自己判断分隔符', () => { assert.match(tools, /const sid = process\.env\.AGENTMAIL_SESSION_ID \|\| ''/, '调用时读,不是 import 时读死'); assert.ok(tools.includes("path.includes('?') ? '&' : '?'"), '已有查询串要用 & 分隔'); assert.match(tools, /if \(!sid\) return path/, '拿不到会话就原样返回,不拼半截 URL'); });