From 2f51041822cdb7e2ef1902979b386e48ffbd3958 Mon Sep 17 00:00:00 2001 From: john Date: Wed, 29 Jul 2026 06:45:22 +0800 Subject: [PATCH] fix: fail closed when WeChat page.generate has no HTML artifact Prevent text-only planning replies from marking WeChat delivery done when static-page-publish never confirmed a public HTML file. Co-authored-by: Cursor --- wechat-mp.test.mjs | 11 ++++++----- wechat/handlers/page-generate.mjs | 8 +++++++- wechat/wechat-channel.test.mjs | 26 ++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/wechat-mp.test.mjs b/wechat-mp.test.mjs index 640aa91..57c8c8a 100644 --- a/wechat-mp.test.mjs +++ b/wechat-mp.test.mjs @@ -3288,8 +3288,8 @@ test('wechat mp service forwards H5 agent text when page generation produced no assert.equal(fs.existsSync(htmlPath), false); const sendCall = wechatCalls.find(([url]) => String(url).includes('/cgi-bin/message/custom/send')); const payload = JSON.parse(sendCall[2]); - assert.match(payload.text.content, /🌴 泰国简易攻略/); - assert.doesNotMatch(payload.text.content, /没有按 H5 里的页面技能真正生成成功/); + assert.doesNotMatch(payload.text.content, /🌴 泰国简易攻略/); + assert.match(payload.text.content, /没有按服务号页面技能真正生成成功/); assert.doesNotMatch(payload.text.content, /https:\/\/m\.tkmind\.cn\/MindSpace\/.+\/public\/thailand-guide\.html/); }); @@ -3631,7 +3631,8 @@ test('wechat mp service recreates dedicated session when tool_calls error arrive assert.equal(started, true); const sendCall = wechatCalls.find(([url]) => String(url).includes('/cgi-bin/message/custom/send')); const payload = JSON.parse(sendCall[2]); - assert.match(payload.text.content, /已恢复,可以继续对话/); + assert.doesNotMatch(payload.text.content, /已恢复,可以继续对话/); + assert.match(payload.text.content, /没有按服务号页面技能真正生成成功/); assert.doesNotMatch(payload.text.content, /Bad request/); assert.doesNotMatch(payload.text.content, /tool_calls/); }); @@ -3965,8 +3966,8 @@ test('wechat mp service retries poisoned publish claims before forwarding H5 ret const sendCall = wechatCalls.find(([url]) => String(url).includes('/cgi-bin/message/custom/send')); const payload = JSON.parse(sendCall[2]); assert.doesNotMatch(payload.text.content, /页面都已经成功发布了|主题页面已发布/); - assert.match(payload.text.content, /🌴 夏日主题页面/); - assert.doesNotMatch(payload.text.content, /没有按 H5 里的页面技能真正生成成功/); + assert.doesNotMatch(payload.text.content, /🌴 夏日主题页面/); + assert.match(payload.text.content, /没有按服务号页面技能真正生成成功/); assert.doesNotMatch(payload.text.content, /summer-breeze-journal\.html/); }); diff --git a/wechat/handlers/page-generate.mjs b/wechat/handlers/page-generate.mjs index 29ce593..d07546f 100644 --- a/wechat/handlers/page-generate.mjs +++ b/wechat/handlers/page-generate.mjs @@ -69,5 +69,11 @@ export function resolvePageGenerateOutcome({ }; } - return { action: 'send', artifacts: sendable }; + // Fail closed: page.generate must deliver a verified public HTML artifact. + // Text-only planning replies ("我先搜索…") must not mark WeChat delivery done. + return { + action: 'fail', + failureText: buildPagePublishFailureText(), + reason: 'missing_page_artifact', + }; } diff --git a/wechat/wechat-channel.test.mjs b/wechat/wechat-channel.test.mjs index 39e090e..a24e596 100644 --- a/wechat/wechat-channel.test.mjs +++ b/wechat/wechat-channel.test.mjs @@ -218,3 +218,29 @@ test('resolvePageGenerateOutcome sends when share preview meta is present', () = assert.equal(outcome.action, 'send'); assert.equal(outcome.artifacts.length, 1); }); + +test('resolvePageGenerateOutcome fails closed on planning text without html artifact', () => { + const outcome = resolvePageGenerateOutcome({ + reply: { + text: '找到了之前的新闻页面。让我先读取最新的页面格式作为参考,同时并行搜索今日新闻和天气。', + }, + confirmedArtifacts: [], + verifiedArtifacts: [], + }); + assert.equal(outcome.action, 'fail'); + assert.equal(outcome.reason, 'missing_page_artifact'); + assert.match(outcome.failureText, /没有按服务号页面技能真正生成成功|static-page-publish/); +}); + +test('resolvePageGenerateOutcome fails closed when reply links an old page but no artifact confirmed', () => { + const outcome = resolvePageGenerateOutcome({ + reply: { + text: '[每日新闻](https://m.tkmind.cn/MindSpace/u/public/daily-news-0728.html)', + }, + confirmedArtifacts: [], + verifiedArtifacts: [], + replyHasPublicLinks: true, + }); + assert.equal(outcome.action, 'fail'); + assert.equal(outcome.reason, 'missing_page_artifact'); +});