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 <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-29 06:45:22 +08:00
parent b553107817
commit 2f51041822
3 changed files with 39 additions and 6 deletions
+6 -5
View File
@@ -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/);
});
+7 -1
View File
@@ -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',
};
}
+26
View File
@@ -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');
});