/** * 投递提示词不能把模型引向 `read_inbox`(dsh)。 * * # 为什么不只是"措辞过时" * * 投递时 `markDelivered` 就把信标成已读(`src/index.ts:505` 的定义, * `:551`/`:2214`/`:2276` 三处调用,SSE 主投递与 catchUp 都走它 —— * 2026-09-26 为治"重启重投→回声"定的性)。于是: * * 提示词说「调 read_inbox 读正文」 ⇒ read_inbox 默认 status=unread * ⇒ 看不到刚投递的那封 ⇒ 返回空。 * * **危险的形状**不是"白费一次调用",是模型看到空之后以为"没有新邮件" * 就结束回合 —— 那样会**静默丢掉一个真实请求**。mail_id 就在同一段 * 提示词里,`read_mail` 能拿到正文与附件清单。 * * # 钉住三件事 * * 1. 投递提示词指向 read_mail,且带得出 mail_id(提示词里有「邮件 ID」行)。 * 2. read_inbox 的工具描述不再说"收到新邮件通知后应立即调用此工具" * —— 那句是模型看到的第一句话,只改提示词不改它,模型照样走偏。 * 3. 三条建会话路径(adoptPrompt / resume 复用 / 新开会话)**各带一份** * 文案。只改其中一处 = 另外两条路径上的会话照样走偏。dsh 桥曾 * 长期是 3 处而外部只数出 1 处。 * * 反向对照:必须同时断言「旧文案不存在」与「新文案存在」, * 否则把提示词整段删空也能过。 */ 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, '..', 'src', 'index.ts'), 'utf8'); // dist 是**真正被 dsh 加载**的那份(package.json main: dist/index.js)。 // 改了 src 忘了 build 就是"源码对、线上旧代码"。 const dist = readFileSync(join(HERE, '..', 'dist', 'index.js'), 'utf8'); test('★ 投递提示词不再让模型先调 read_inbox', () => { assert.ok( !src.includes('请先调用 read_inbox'), 'src 里还有旧文案:投递时信已标为已读,read_inbox 默认只看未读,读不到', ); assert.ok(!dist.includes('请先调用 read_inbox'), 'dist 里还有 —— 忘了 npm run build?'); }); test('★ 新文案指向 read_mail 并说明为什么不用 read_inbox', () => { const hits = src.match(/请用 read_mail\(mail_id 用上面「邮件 ID」那处\)/g) || []; assert.equal(hits.length, 3, `三条建会话路径各带一份文案,应为 3 处,实际 ${hits.length}`); assert.ok( src.includes('这封信在投递时已标为已读'), '必须说明原因,否则模型可能"顺手"再去调一次 read_inbox', ); assert.ok(src.includes('read_inbox 只用来看本会话的其它未读'), '要写清 read_inbox 仍有什么用途'); assert.equal( (dist.match(/请用 read_mail\(mail_id 用上面「邮件 ID」那处\)/g) || []).length, 3, 'dist 里应是 3 处', ); }); test('★ read_inbox 的工具描述不再声称"收到新邮件通知后应立即调用"', () => { assert.ok( !src.includes('收到新邮件通知后应立即调用'), '那句与「投递即标已读」直接矛盾,且它是模型看到的第一句话', ); assert.ok(!dist.includes('收到新邮件通知后应立即调用'), 'dist 里还有 —— 忘了 npm run build?'); assert.ok( src.includes('刚投递到本会话的那封已在投递时标为已读'), 'read_inbox 描述要说明它为什么没有刚投递的那封', ); }); test('★ read_mail 的描述指明「刚投递的那封读这个」', () => { assert.ok( src.includes('刚投递到本会话的那封邮件就读这个'), '两个工具描述都在往 read_inbox 上引时,光改提示词不够', ); }); test('★ 附件提示保留(新开会话那份原文有 download_attachment 指引)', () => { // 新开会话分支原本是唯一带附件说明的;改写时不能顺手丢掉。 assert.ok(src.includes('有附件可用 download_attachment 取回'), '附件取回指引丢了'); });