diff --git a/wechat-mp.mjs b/wechat-mp.mjs
index 73f126b..2afa41d 100644
--- a/wechat-mp.mjs
+++ b/wechat-mp.mjs
@@ -35,6 +35,10 @@ const SESSION_WORKSPACE_TOOL_NAMES = new Set([
'list_dir',
]);
+function escapeRegExp(value) {
+ return String(value ?? '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+}
+
function parseXmlField(xml, field) {
const cdataMatch = xml.match(new RegExp(`<${field}><\\/${field}>`));
if (cdataMatch) return cdataMatch[1];
@@ -310,7 +314,13 @@ function extractHtmlWriteTargets(messages = []) {
const args = toolCall?.arguments ?? {};
const action = String(args.action ?? '').toLowerCase();
const writeLikeDeveloper = name === 'developer' && action === 'write';
- const writeLikeSandbox = (name === 'write_file' || name === 'edit_file') && typeof args.path === 'string';
+ const normalizedName = String(name ?? '').trim();
+ const writeLikeSandbox =
+ (normalizedName === 'write_file' ||
+ normalizedName === 'edit_file' ||
+ normalizedName.endsWith('__write_file') ||
+ normalizedName.endsWith('__edit_file')) &&
+ typeof args.path === 'string';
if (!writeLikeDeveloper && !writeLikeSandbox) continue;
const candidate = String(args.path ?? '').trim();
if (candidate.toLowerCase().endsWith('.html')) targets.add(candidate);
@@ -325,6 +335,19 @@ function looksLikeHtmlGenerationIntent(text) {
return /(?:生成|创建|做|写|帮我.*(?:生成|创建|做|写)).*(?:html|页面|网页|page|文件)/i.test(normalized);
}
+function usedStaticPagePublishSkill(messages = []) {
+ return messages.some((message) =>
+ message?.content?.some((item) => {
+ if (item?.type !== 'toolRequest') return false;
+ const toolCall = item.toolCall?.value;
+ const name = String(toolCall?.name ?? '').trim();
+ const args = toolCall?.arguments ?? {};
+ const skillName = String(args.name ?? '').trim();
+ return name === 'load_skill' && skillName === 'static-page-publish';
+ }),
+ );
+}
+
function isBareCompletionText(text) {
const normalized = String(text ?? '').trim();
return /^(?:已完成|完成了|完成)$/u.test(normalized);
@@ -343,6 +366,39 @@ function isSuspiciousBareCompletionReply(reply, intent) {
return !hasAnyToolRequest(reply?.messages ?? []);
}
+function looksLikePublishSuccessClaim(text) {
+ const normalized = String(text ?? '').trim();
+ if (!normalized) return false;
+ return /(?:页面|网页|html).*(?:已创建|已生成|已发布|创建完毕|生成完成|发布成功)|(?:成功发布|已经发布|已创建完毕).*(?:页面|网页|html)/iu.test(normalized);
+}
+
+async function hasAnyValidPublishedHtmlLink(text, linkExists) {
+ const value = String(text ?? '');
+ for (const match of value.matchAll(PUBLIC_HTML_LINK_PATTERN)) {
+ try {
+ if (await linkExists(match[0])) return true;
+ } catch {
+ // treat lookup failures as missing links
+ }
+ }
+ return false;
+}
+
+async function isSuspiciousHtmlPublishClaimReply(reply, intent, { linkExists = defaultPublicHtmlLinkExists } = {}) {
+ if (!looksLikeHtmlGenerationIntent(intent?.agentText)) return false;
+ const text = String(reply?.text ?? '').trim();
+ if (!looksLikePublishSuccessClaim(text)) return false;
+ if (extractHtmlWriteTargets(reply?.messages ?? []).length > 0) return false;
+ return !(await hasAnyValidPublishedHtmlLink(text, linkExists));
+}
+
+function isMissingRequiredPublishSkill(reply, intent) {
+ if (!looksLikeHtmlGenerationIntent(intent?.agentText)) return false;
+ const wroteHtml = extractHtmlWriteTargets(reply?.messages ?? []).length > 0;
+ if (!wroteHtml) return false;
+ return !usedStaticPagePublishSkill(reply?.messages ?? []);
+}
+
function buildPublicHtmlUrl(workingDir, relativePath, publicBaseUrl) {
const owner = path.basename(path.resolve(workingDir));
const normalized = relativePath.split(path.sep).map(encodeURIComponent).join('/');
@@ -351,7 +407,9 @@ function buildPublicHtmlUrl(workingDir, relativePath, publicBaseUrl) {
function ensurePublicHtmlArtifact(htmlPath, workingDir) {
const workspaceRoot = path.resolve(workingDir);
- const source = path.resolve(htmlPath);
+ const source = path.isAbsolute(String(htmlPath ?? ''))
+ ? path.resolve(String(htmlPath))
+ : path.resolve(workspaceRoot, String(htmlPath ?? ''));
if (source !== workspaceRoot && !source.startsWith(`${workspaceRoot}${path.sep}`)) return null;
if (!fs.existsSync(source) || !fs.statSync(source).isFile()) return null;
@@ -371,13 +429,52 @@ function ensurePublicHtmlArtifact(htmlPath, workingDir) {
};
}
-async function maybeAttachPublishedHtmlLink(reply, { workingDir, publicBaseUrl }) {
- const baseText = String(reply?.text ?? '').trim();
+function collectPublishedHtmlArtifacts(reply, { workingDir, publicBaseUrl }) {
+ const artifacts = [];
const htmlTargets = extractHtmlWriteTargets(reply?.messages ?? []);
for (const target of htmlTargets) {
const artifact = ensurePublicHtmlArtifact(target, workingDir);
if (!artifact) continue;
- const url = buildPublicHtmlUrl(workingDir, artifact.relativePath, publicBaseUrl);
+ artifacts.push({
+ ...artifact,
+ url: buildPublicHtmlUrl(workingDir, artifact.relativePath, publicBaseUrl),
+ });
+ }
+ return artifacts;
+}
+
+function rewritePublishedHtmlLinks(text, artifacts = []) {
+ const value = String(text ?? '');
+ if (!value || artifacts.length === 0) return value;
+ let next = value;
+ for (const artifact of artifacts) {
+ const canonicalUrl = String(artifact?.url ?? '').trim();
+ const filename = String(artifact?.relativePath ?? '').replace(/\\/g, '/').split('/').pop();
+ if (!canonicalUrl || !filename) continue;
+ const wrongPublicLinkPattern = new RegExp(
+ `https?:\\/\\/[^\\s)]+\\/(?:MindSpace\\/[^\\s/]+\\/)?public\\/${escapeRegExp(filename)}\\b`,
+ 'g',
+ );
+ const wrongTkmindHtmlLinkPattern = new RegExp(
+ `https?:\\/\\/(?:[^\\s./]+\\.)*tkmind\\.cn\\/[^\n\\s)]*${escapeRegExp(filename)}\\b`,
+ 'gi',
+ );
+ next = next.replace(wrongPublicLinkPattern, canonicalUrl);
+ next = next.replace(wrongTkmindHtmlLinkPattern, canonicalUrl);
+ next = next.replace(PUBLIC_HTML_LINK_PATTERN, (match) => {
+ if (match === canonicalUrl) return match;
+ const matchedFilename = String(match).split('/').pop();
+ return matchedFilename === filename ? canonicalUrl : match;
+ });
+ }
+ return next;
+}
+
+async function maybeAttachPublishedHtmlLink(reply, { workingDir, publicBaseUrl }) {
+ const artifacts = collectPublishedHtmlArtifacts(reply, { workingDir, publicBaseUrl });
+ const baseText = rewritePublishedHtmlLinks(String(reply?.text ?? '').trim(), artifacts).trim();
+ for (const artifact of artifacts) {
+ const url = artifact.url;
if (baseText.includes(url)) return baseText;
return baseText
? `${baseText}\n\n查看页面:\n${url}`
@@ -520,6 +617,18 @@ export async function guardMissingPublicHtmlLinks(
return replacements.reduce((next, [from, to]) => next.replaceAll(from, to), value);
}
+function downgradePrematurePublishClaims(text) {
+ const value = String(text ?? '');
+ if (!value.includes('页面生成未完成')) return value;
+ return value
+ .replace(/(^|\n)([^\n]*页面都已经成功发布了[^\n]*)(?=\n|$)/g, '$1页面暂未生成完成,这次我先不发送失效链接。')
+ .replace(/(^|\n)([^\n]*主题页面已发布[^\n]*)(?=\n|$)/g, '$1🌴 夏日主题页面生成未完成')
+ .replace(/页面已创建完毕/g, '页面生成未完成')
+ .replace(/页面已创建/g, '页面生成未完成')
+ .replace(/发布成功/g, '生成未完成')
+ .replace(/已发布成功/g, '生成未完成');
+}
+
export { maybeAttachPublishedHtmlLink };
function isQuestionStatusProbe(text) {
@@ -676,6 +785,15 @@ function normalizeWechatInboundIntent(inbound) {
function buildWechatAgentPrompt(intent) {
const msgType = String(intent?.msgType ?? 'text');
+ const pagePublishHint = looksLikeHtmlGenerationIntent(intent?.agentText ?? intent?.content)
+ ? [
+ '【页面发布技能要求】这条消息是在生成可访问 HTML 页面。',
+ '开始前必须先调用 `load_skill` → `static-page-publish`,不能省略,也不能写完页面后再补调。',
+ '随后必须用 `write_file` / `edit_file` 写入 `public/*.html`。',
+ '最终只给用户一个正式域名的唯一正确链接;不要输出错误域名、备用链接或让用户手动保存文件。',
+ '',
+ ].join('\n')
+ : '';
const scheduleAssistantHint = shouldUseScheduleAssistant(intent?.agentText ?? intent?.content)
? [
'【日程技能要求】这条消息涉及待办、提醒或日程。',
@@ -737,6 +855,7 @@ function buildWechatAgentPrompt(intent) {
}
const content = String(intent?.agentText ?? intent?.content ?? '').trim();
const lines = [];
+ if (pagePublishHint) lines.push(pagePublishHint);
if (scheduleAssistantHint) lines.push(scheduleAssistantHint);
lines.push(
'【微信服务号新消息】请只回答下面这条用户消息,不要主动延续无关的历史话题。',
@@ -951,9 +1070,11 @@ export function createWechatMpService({
config,
userAuth,
apiFetch,
+ startAgentSession = null,
sessionApiFetch = null,
scheduleService = null,
applySessionLlmProvider = null,
+ refreshSessionSnapshot = null,
wechatFetch = undiciFetch,
linkExists = defaultPublicHtmlLinkExists,
logger = console,
@@ -1089,9 +1210,21 @@ export function createWechatMpService({
};
};
- const sendCustomerServiceText = async (openid, content, user = null) => {
+ const sendCustomerServiceText = async (openid, content, user = null, { verifiedHtmlUrls = [] } = {}) => {
const formatted = formatWechatOutboundText(content, user);
- const guarded = await guardMissingPublicHtmlLinks(formatted, { linkExists });
+ const verifiedUrlSet = new Set(
+ verifiedHtmlUrls
+ .map((url) => String(url ?? '').trim())
+ .filter(Boolean),
+ );
+ const guarded = downgradePrematurePublishClaims(
+ await guardMissingPublicHtmlLinks(formatted, {
+ linkExists: async (url) => {
+ if (verifiedUrlSet.has(String(url ?? '').trim())) return true;
+ return linkExists(url);
+ },
+ }),
+ );
const chunks = splitWechatText(guarded);
const accessToken = await getStableAccessToken();
for (const chunk of chunks) {
@@ -1216,26 +1349,28 @@ export function createWechatMpService({
if (!gate.ok) {
throw new Error(gate.message || '当前用户无法使用聊天能力');
}
- const started = await readJsonResponse(
- await apiFetch('/agent/start', {
- method: 'POST',
- body: JSON.stringify({
- working_dir: workingDir,
- enable_context_memory: sessionPolicy.enableContextMemory,
- ...(sessionPolicy.extensionOverrides
- ? { extension_overrides: sessionPolicy.extensionOverrides }
- : {}),
- }),
- }),
- );
+ const started = startAgentSession
+ ? await startAgentSession({ userId, workingDir, sessionPolicy })
+ : await readJsonResponse(
+ await apiFetch('/agent/start', {
+ method: 'POST',
+ body: JSON.stringify({
+ working_dir: workingDir,
+ enable_context_memory: sessionPolicy.enableContextMemory,
+ ...(sessionPolicy.extensionOverrides
+ ? { extension_overrides: sessionPolicy.extensionOverrides }
+ : {}),
+ }),
+ }),
+ );
const sessionId = started?.id;
if (!sessionId) {
throw new Error('公众号专属 Agent 会话创建失败');
}
- // `/agent/start` already persists the owning user and goosed node via the
- // portal proxy. Re-registering here without the node can overwrite the
- // correct mapping back to node 0 in multi-goosed production.
+ if (!startAgentSession && typeof userAuth.registerAgentSession === 'function') {
+ await userAuth.registerAgentSession(userId, sessionId);
+ }
await reconcileAgentSession(
(pathname, init) => fetchForSession(sessionId, pathname, init),
sessionId,
@@ -1263,6 +1398,15 @@ export function createWechatMpService({
return sessionId;
};
+ const refreshWechatSessionSnapshot = async (sessionId, userId) => {
+ if (!sessionId || !userId || typeof refreshSessionSnapshot !== 'function') return;
+ try {
+ await refreshSessionSnapshot(sessionId, userId);
+ } catch (err) {
+ logger.warn?.('WeChat MP snapshot refresh failed:', err);
+ }
+ };
+
const rememberWechatUserContext = async (sessionId, user) => {
const addressName = resolveWechatAddressName(user);
if (!addressName) return;
@@ -1383,17 +1527,29 @@ export function createWechatMpService({
buildWechatAgentPrompt(intent),
buildIntentMetadata(intent),
);
- if (isSuspiciousBareCompletionReply(reply, intent)) {
+ if (
+ isMissingRequiredPublishSkill(reply, intent) ||
+ isSuspiciousBareCompletionReply(reply, intent) ||
+ (await isSuspiciousHtmlPublishClaimReply(reply, intent, { linkExists }))
+ ) {
throw new Error('stale_session_poisoned_completion');
}
if (reply.tokenState) {
await userAuth.billSessionUsage(user.userId, sessionId, reply.tokenState, requestId);
}
- const finalizedReply = await maybeAttachPublishedHtmlLink(reply, {
- workingDir: publishLayout?.publishDir ?? (await userAuth.resolveWorkingDir(user.userId)),
+ const workingDir = publishLayout?.publishDir ?? (await userAuth.resolveWorkingDir(user.userId));
+ const publishedArtifacts = collectPublishedHtmlArtifacts(reply, {
+ workingDir,
publicBaseUrl: config.publicBaseUrl,
});
- await sendCustomerServiceText(inbound.fromUserName, await guardScheduleReply(finalizedReply), user);
+ const finalizedReply = await maybeAttachPublishedHtmlLink(reply, {
+ workingDir,
+ publicBaseUrl: config.publicBaseUrl,
+ });
+ await refreshWechatSessionSnapshot(sessionId, user.userId);
+ await sendCustomerServiceText(inbound.fromUserName, await guardScheduleReply(finalizedReply), user, {
+ verifiedHtmlUrls: publishedArtifacts.map((artifact) => artifact.url),
+ });
return { sessionId };
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
@@ -1416,17 +1572,29 @@ export function createWechatMpService({
buildWechatAgentPrompt(intent),
buildIntentMetadata(intent),
);
- if (isSuspiciousBareCompletionReply(reply, intent)) {
+ if (
+ isMissingRequiredPublishSkill(reply, intent) ||
+ isSuspiciousBareCompletionReply(reply, intent) ||
+ (await isSuspiciousHtmlPublishClaimReply(reply, intent, { linkExists }))
+ ) {
throw new Error('本轮命中了被旧指令污染的专属会话,请稍后重试');
}
if (reply.tokenState) {
await userAuth.billSessionUsage(user.userId, sessionId, reply.tokenState, retryId);
}
- const finalizedReply = await maybeAttachPublishedHtmlLink(reply, {
- workingDir: publishLayout?.publishDir ?? (await userAuth.resolveWorkingDir(user.userId)),
+ const workingDir = publishLayout?.publishDir ?? (await userAuth.resolveWorkingDir(user.userId));
+ const publishedArtifacts = collectPublishedHtmlArtifacts(reply, {
+ workingDir,
publicBaseUrl: config.publicBaseUrl,
});
- await sendCustomerServiceText(inbound.fromUserName, await guardScheduleReply(finalizedReply), user);
+ const finalizedReply = await maybeAttachPublishedHtmlLink(reply, {
+ workingDir,
+ publicBaseUrl: config.publicBaseUrl,
+ });
+ await refreshWechatSessionSnapshot(sessionId, user.userId);
+ await sendCustomerServiceText(inbound.fromUserName, await guardScheduleReply(finalizedReply), user, {
+ verifiedHtmlUrls: publishedArtifacts.map((artifact) => artifact.url),
+ });
return { sessionId };
}
throw err;
diff --git a/wechat-mp.test.mjs b/wechat-mp.test.mjs
index 52c47fd..83fa587 100644
--- a/wechat-mp.test.mjs
+++ b/wechat-mp.test.mjs
@@ -138,6 +138,79 @@ test('guardMissingPublicHtmlLinks blocks missing MindSpace public html links', a
assert.match(guarded, /missing\.html/);
});
+test('wechat mp service rejects premature publish success copy when page link is blocked', async () => {
+ const wechatCalls = [];
+ const service = createBoundWechatService({
+ config: {
+ publicBaseUrl: 'https://m.tkmind.cn',
+ },
+ sessionApiFetch: async (sessionId, pathname) => {
+ assert.equal(sessionId, 'session-1');
+ if (pathname === '/sessions/session-1/events') {
+ return new Response(
+ [
+ 'data: {"type":"Message","request_id":"req-1","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"好消息!m.tkmind.cn 其实是可以访问的,页面都已经成功发布了!以下是您的夏日主题页面:\\n\\n🌴 夏日主题页面已发布\\n\\nhttps://m.tkmind.cn/MindSpace/john/public/summer-breeze-journal.html"}]}}\n\n',
+ 'data: {"type":"Finish","request_id":"req-1","token_state":{"inputTokens":2,"outputTokens":2}}\n\n',
+ ].join(''),
+ { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
+ );
+ }
+ if (pathname === '/sessions/session-1/reply') {
+ return new Response(JSON.stringify({ status: 'ok' }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
+ return new Response(JSON.stringify({ ok: true }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ throw new Error(`unexpected session api path: ${sessionId} ${pathname}`);
+ },
+ wechatFetch: async (url, init = {}) => {
+ wechatCalls.push([url, init.method ?? 'GET', init.body ?? null]);
+ if (String(url).includes('/cgi-bin/stable_token')) {
+ return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ if (String(url).includes('/cgi-bin/message/custom/send')) {
+ return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ throw new Error(`unexpected wechat url: ${url}`);
+ },
+ userAuth: {
+ async resolveWorkingDir() {
+ return '/tmp/user-1';
+ },
+ },
+ });
+ const originalRandomUuid = crypto.randomUUID;
+ crypto.randomUUID = () => 'req-1';
+ try {
+ const result = await service.handleInboundMessage(inboundXml({ content: '帮我生成一个夏日页面' }), {
+ timestamp: '1710000000',
+ nonce: 'nonce',
+ signature: signatureFor('token', '1710000000', 'nonce'),
+ });
+ assert.equal(result.status, 200);
+ await result.task;
+ } finally {
+ crypto.randomUUID = originalRandomUuid;
+ }
+
+ 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, /旧指令污染|稍后重试|转发到专属 Agent 失败/);
+});
+
test('maybeAttachPublishedHtmlLink copies generated root html into public and returns link', async (t) => {
const workspaceRoot = fs.mkdtempSync('/tmp/wechat-mp-public-');
const htmlPath = `${workspaceRoot}/hello.html`;
@@ -180,6 +253,131 @@ test('maybeAttachPublishedHtmlLink copies generated root html into public and re
);
});
+test('maybeAttachPublishedHtmlLink recognizes sandbox-fs write_file html outputs', async () => {
+ const workspaceRoot = fs.mkdtempSync('/tmp/wechat-mp-sandbox-fs-');
+ const htmlPath = `${workspaceRoot}/public/codex-verify.html`;
+ fs.mkdirSync(`${workspaceRoot}/public`, { recursive: true });
+ fs.writeFileSync(htmlPath, '
Codex Verify');
+
+ const text = await maybeAttachPublishedHtmlLink(
+ {
+ text: '页面已生成 ✅',
+ messages: [
+ {
+ role: 'assistant',
+ content: [
+ {
+ type: 'toolRequest',
+ toolCall: {
+ value: {
+ name: 'sandbox-fs__write_file',
+ arguments: {
+ path: 'public/codex-verify.html',
+ content: 'Codex Verify',
+ },
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+ {
+ workingDir: workspaceRoot,
+ publicBaseUrl: 'https://m.tkmind.cn',
+ },
+ );
+
+ assert.match(
+ text,
+ /https:\/\/m\.tkmind\.cn\/MindSpace\/.+\/public\/codex-verify\.html/,
+ );
+});
+
+test('maybeAttachPublishedHtmlLink rewrites wrong public html links to the canonical published url', async () => {
+ const workspaceRoot = fs.mkdtempSync('/tmp/wechat-mp-rewrite-link-');
+ const htmlPath = `${workspaceRoot}/public/summer-breeze-journal.html`;
+ fs.mkdirSync(`${workspaceRoot}/public`, { recursive: true });
+ fs.writeFileSync(htmlPath, 'Summer');
+
+ const text = await maybeAttachPublishedHtmlLink(
+ {
+ text: '页面已发布:https://m.tkmind.cn/public/summer-breeze-journal.html',
+ messages: [
+ {
+ role: 'assistant',
+ content: [
+ {
+ type: 'toolRequest',
+ toolCall: {
+ value: {
+ name: 'sandbox-fs__write_file',
+ arguments: {
+ path: 'public/summer-breeze-journal.html',
+ content: 'Summer',
+ },
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+ {
+ workingDir: workspaceRoot,
+ publicBaseUrl: 'https://m.tkmind.cn',
+ },
+ );
+
+ assert.doesNotMatch(text, /https:\/\/m\.tkmind\.cn\/public\/summer-breeze-journal\.html/);
+ assert.match(
+ text,
+ /https:\/\/m\.tkmind\.cn\/MindSpace\/.+\/public\/summer-breeze-journal\.html/,
+ );
+});
+
+test('maybeAttachPublishedHtmlLink rewrites tkmind domain variants to the canonical published url', async () => {
+ const workspaceRoot = fs.mkdtempSync('/tmp/wechat-mp-rewrite-domain-');
+ const htmlPath = `${workspaceRoot}/public/codex-prod-check-v6.html`;
+ fs.mkdirSync(`${workspaceRoot}/public`, { recursive: true });
+ fs.writeFileSync(htmlPath, 'Prod Check');
+
+ const text = await maybeAttachPublishedHtmlLink(
+ {
+ text: '唯一可访问链接:https://mindspace.tkmind.cn/a70ff537-8908-486e-9b6c-042e07cc25db/codex-prod-check-v6.html',
+ messages: [
+ {
+ role: 'assistant',
+ content: [
+ {
+ type: 'toolRequest',
+ toolCall: {
+ value: {
+ name: 'sandbox-fs__write_file',
+ arguments: {
+ path: 'public/codex-prod-check-v6.html',
+ content: 'Prod Check',
+ },
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+ {
+ workingDir: workspaceRoot,
+ publicBaseUrl: 'https://m.tkmind.cn',
+ },
+ );
+
+ assert.doesNotMatch(text, /https:\/\/mindspace\.tkmind\.cn\/.+\/codex-prod-check-v6\.html/);
+ assert.match(
+ text,
+ /https:\/\/m\.tkmind\.cn\/MindSpace\/.+\/public\/codex-prod-check-v6\.html/,
+ );
+});
+
test('wechat mp service splits long agent replies into multiple customer messages', async () => {
const token = 'token';
const timestamp = '1710000000';
@@ -388,6 +586,10 @@ test('wechat mp service strips markdown emphasis around outbound links', async (
}
throw new Error(`unexpected wechat url: ${url}`);
},
+ linkExists: async (urlText) => {
+ if (!String(urlText).includes('/summer-breeze-journal.html')) return false;
+ return fs.existsSync(htmlPath);
+ },
});
const originalRandomUuid = crypto.randomUUID;
@@ -452,6 +654,10 @@ test('wechat mp service does not send stale page links from unscoped conversatio
}
throw new Error(`unexpected wechat url: ${url}`);
},
+ linkExists: async (urlText) => {
+ if (!String(urlText).includes('/summer-breeze-journal.html')) return false;
+ return fs.existsSync(htmlPath);
+ },
});
const originalRandomUuid = crypto.randomUUID;
@@ -647,7 +853,8 @@ test('wechat mp service routes text to dedicated session and sends customer serv
}
if (pathname === '/sessions/session-1/reply') {
const body = JSON.parse(init.body);
- assert.equal(body.user_message.content[0].text, '【微信服务号新消息】请只回答下面这条用户消息,不要主动延续无关的历史话题。\n若用户只是在测试连通性,请一句话确认收到即可,不要展开旧任务。\n\n用户消息:帮我做个页面');
+ assert.match(body.user_message.content[0].text, /用户消息:帮我做个页面/);
+ assert.match(body.user_message.content[0].text, /static-page-publish/);
body.request_id && (apiCalls[apiCalls.length - 1][2] = body.request_id);
return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
}
@@ -696,6 +903,10 @@ test('wechat mp service routes text to dedicated session and sends customer serv
}
throw new Error(`unexpected wechat url: ${url}`);
},
+ linkExists: async (urlText) => {
+ if (!String(urlText).includes('/summer-breeze-journal.html')) return false;
+ return fs.existsSync(htmlPath);
+ },
});
const originalRandomUuid = crypto.randomUUID;
@@ -808,6 +1019,10 @@ test('wechat mp service rewrites internal wx username mentions that appear mid-m
}
throw new Error(`unexpected wechat url: ${url}`);
},
+ linkExists: async (urlText) => {
+ if (!String(urlText).includes('/summer-breeze-journal.html')) return false;
+ return fs.existsSync(htmlPath);
+ },
});
const originalRandomUuid = crypto.randomUUID;
@@ -895,6 +1110,90 @@ test('wechat mp service applies provider before forwarding to dedicated session'
assert.deepEqual(appliedSessions, ['session-1']);
});
+test('wechat mp service injects static page publish instructions for html generation requests', async () => {
+ const token = 'token';
+ const timestamp = '1710000000';
+ const nonce = 'nonce';
+ const service = createBoundWechatService({
+ token,
+ userAuth: {
+ async findWechatUserByOpenid() {
+ return { userId: 'user-1', status: 'active', nickname: '毕升' };
+ },
+ async recordWechatMpMessage() {
+ return { inserted: true };
+ },
+ async insertWechatMpMessageDetail() {},
+ async finishWechatMpMessage() {},
+ async resolveWorkingDir() {
+ return '/tmp/user-1';
+ },
+ async getAgentSessionPolicy() {
+ return { enableContextMemory: false, extensionOverrides: [], unrestricted: true };
+ },
+ async getUserPublishLayout() {
+ return { displayName: 'John', username: 'john', slug: 'john', constraints: null };
+ },
+ async registerAgentSession() {},
+ async upsertWechatAgentRoute() {},
+ async billSessionUsage() {},
+ },
+ sessionApiFetch: async (sessionId, pathname, init = {}) => {
+ assert.equal(sessionId, 'session-1');
+ if (pathname === '/sessions/session-1/events') {
+ return new Response(
+ [
+ 'data: {"type":"Message","request_id":"req-page","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"收到,我去生成页面。"}]}}\n\n',
+ 'data: {"type":"Finish","request_id":"req-page","token_state":{"inputTokens":1,"outputTokens":2}}\n\n',
+ ].join(''),
+ { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
+ );
+ }
+ if (pathname === '/sessions/session-1/reply') {
+ const body = JSON.parse(init.body);
+ assert.match(body.user_message.content[0].text, /static-page-publish/);
+ assert.match(body.user_message.content[0].text, /必须先调用 `load_skill`/);
+ assert.match(body.user_message.content[0].text, /写入 `public\/\*\.html`/);
+ assert.match(body.user_message.content[0].text, /唯一正确链接/);
+ return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
+ }
+ if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
+ return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
+ }
+ throw new Error(`unexpected api path: ${pathname}`);
+ },
+ wechatFetch: async (url) => {
+ if (String(url).includes('/cgi-bin/stable_token')) {
+ return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ if (String(url).includes('/cgi-bin/message/custom/send')) {
+ return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ throw new Error(`unexpected wechat url: ${url}`);
+ },
+ });
+
+ const originalRandomUuid = crypto.randomUUID;
+ crypto.randomUUID = () => 'req-page';
+ try {
+ const result = await service.handleInboundMessage(inboundXml({ content: '请生成一个 html 页面,文件名 public/hello-world.html' }), {
+ timestamp,
+ nonce,
+ signature: signatureFor(token, timestamp, nonce),
+ });
+ assert.equal(result.status, 200);
+ await result.task;
+ } finally {
+ crypto.randomUUID = originalRandomUuid;
+ }
+});
+
test('wechat mp service injects schedule skill instructions for reminder-like requests', async () => {
const token = 'token';
const timestamp = '1710000000';
@@ -1259,6 +1558,7 @@ test('wechat mp service recreates poisoned dedicated session after bare completi
}
return new Response(
[
+ 'data: {"type":"Message","request_id":"req-poisoned-retry","message":{"id":"assistant-skill","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"toolRequest","toolCall":{"value":{"name":"load_skill","arguments":{"name":"static-page-publish"}}}}]}}\n\n',
`data: {"type":"Message","request_id":"req-poisoned-retry","message":{"id":"assistant-tool","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"toolRequest","toolCall":{"value":{"name":"developer","arguments":{"action":"write","path":"${htmlPath.replace(/\\/g, '\\\\')}","content":"Hello"}}}}]}}\n\n`,
'data: {"type":"Message","request_id":"req-poisoned-retry","message":{"id":"assistant-2","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"已完成"}]}}\n\n',
'data: {"type":"Finish","request_id":"req-poisoned-retry","token_state":{"inputTokens":2,"outputTokens":2}}\n\n',
@@ -1284,6 +1584,10 @@ test('wechat mp service recreates poisoned dedicated session after bare completi
}
throw new Error(`unexpected wechat url: ${url}`);
},
+ linkExists: async (urlText) => {
+ if (!String(urlText).includes('/summer-breeze-journal.html')) return false;
+ return fs.existsSync(htmlPath);
+ },
});
const originalRandomUuid = crypto.randomUUID;
@@ -1314,6 +1618,355 @@ test('wechat mp service recreates poisoned dedicated session after bare completi
assert.match(payload.text.content, /页面生成未完成|hello\.html/);
});
+test('wechat mp service recreates dedicated session when html was written before loading static-page-publish skill', async () => {
+ const token = 'token';
+ const timestamp = '1710000000';
+ const nonce = 'nonce';
+ const wechatCalls = [];
+ const workspaceRoot = fs.mkdtempSync('/tmp/wechat-mp-missing-skill-');
+ const htmlPath = path.join(workspaceRoot, 'public', 'hello-world.html');
+ let routeCleared = false;
+ let started = false;
+
+ const service = createWechatMpService({
+ config: {
+ enabled: true,
+ appId: 'wx123',
+ appSecret: 'secret',
+ token,
+ publicBaseUrl: 'https://m.tkmind.cn',
+ bindPath: '/auth/wechat/authorize?intent=login',
+ ackText: 'ack',
+ unsupportedText: 'unsupported',
+ unboundTextPrefix: '请先绑定',
+ progressDelayMs: 0,
+ },
+ userAuth: {
+ async findWechatUserByOpenid() {
+ return { userId: 'user-1', status: 'active', nickname: '毕升' };
+ },
+ async getWechatAgentRoute() {
+ return routeCleared ? null : { agentSessionId: 'session-1' };
+ },
+ async clearWechatAgentRoute() {
+ routeCleared = true;
+ },
+ async canUseChat() {
+ return { ok: true };
+ },
+ async resolveWorkingDir() {
+ return workspaceRoot;
+ },
+ async getAgentSessionPolicy() {
+ return {
+ enableContextMemory: false,
+ extensionOverrides: [
+ {
+ type: 'platform',
+ name: 'developer',
+ available_tools: ['write'],
+ },
+ ],
+ unrestricted: false,
+ };
+ },
+ async getUserPublishLayout() {
+ return { publishDir: workspaceRoot, displayName: 'John', username: 'john', slug: 'john', constraints: null };
+ },
+ async registerAgentSession() {},
+ async upsertWechatAgentRoute({ agentSessionId }) {
+ assert.equal(agentSessionId, 'session-2');
+ },
+ async billSessionUsage() {},
+ async recordWechatMpMessage() {
+ return { inserted: true };
+ },
+ async finishWechatMpMessage() {},
+ async insertWechatMpMessageDetail() {},
+ },
+ apiFetch: async (pathname, init = {}) => {
+ if (pathname === '/agent/start') {
+ started = true;
+ const body = JSON.parse(init.body);
+ assert.equal(body.working_dir, workspaceRoot);
+ return new Response(JSON.stringify({ id: 'session-2' }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ throw new Error(`unexpected api path: ${pathname}`);
+ },
+ sessionApiFetch: async (sessionId, pathname) => {
+ if (pathname === `/sessions/${sessionId}/extensions`) {
+ return new Response(JSON.stringify({ extensions: [{ name: 'developer', available_tools: ['write'] }] }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ if (pathname === '/agent/update_working_dir' || pathname === '/agent/update_session' || pathname === '/agent/restart') {
+ return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
+ }
+ if (pathname === '/agent/add_extension') {
+ return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
+ }
+ if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
+ return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
+ }
+ if (pathname === `/sessions/${sessionId}`) {
+ return new Response(JSON.stringify({ working_dir: workspaceRoot }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ if (pathname === `/sessions/${sessionId}/reply`) {
+ if (sessionId === 'session-2') {
+ fs.mkdirSync(path.dirname(htmlPath), { recursive: true });
+ fs.writeFileSync(htmlPath, 'Hello');
+ }
+ return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
+ }
+ if (pathname === `/sessions/${sessionId}/events`) {
+ if (sessionId === 'session-1') {
+ return new Response(
+ [
+ 'data: {"type":"Message","request_id":"req-skill-miss","message":{"id":"assistant-tool","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"toolRequest","toolCall":{"value":{"name":"sandbox-fs__write_file","arguments":{"path":"public/hello-world.html","content":"Hello"}}}}]}}\n\n',
+ 'data: {"type":"Message","request_id":"req-skill-miss","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"页面已生成:https://m.tkmind.cn/MindSpace/john/public/hello-world.html"}]}}\n\n',
+ 'data: {"type":"Finish","request_id":"req-skill-miss","token_state":{"inputTokens":1,"outputTokens":1}}\n\n',
+ ].join(''),
+ { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
+ );
+ }
+ return new Response(
+ [
+ 'data: {"type":"Message","request_id":"req-skill-retry","message":{"id":"assistant-skill","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"toolRequest","toolCall":{"value":{"name":"load_skill","arguments":{"name":"static-page-publish"}}}}]}}\n\n',
+ 'data: {"type":"Message","request_id":"req-skill-retry","message":{"id":"assistant-tool","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"toolRequest","toolCall":{"value":{"name":"sandbox-fs__write_file","arguments":{"path":"public/hello-world.html","content":"Hello"}}}}]}}\n\n',
+ 'data: {"type":"Message","request_id":"req-skill-retry","message":{"id":"assistant-2","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"[Hello World](https://m.tkmind.cn/MindSpace/user-1/public/hello-world.html)"}]}}\n\n',
+ 'data: {"type":"Finish","request_id":"req-skill-retry","token_state":{"inputTokens":2,"outputTokens":2}}\n\n',
+ ].join(''),
+ { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
+ );
+ }
+ throw new Error(`unexpected session api path: ${sessionId} ${pathname}`);
+ },
+ wechatFetch: async (url, init = {}) => {
+ wechatCalls.push([url, init.method ?? 'GET', init.body ?? null]);
+ if (String(url).includes('/cgi-bin/stable_token')) {
+ return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ if (String(url).includes('/cgi-bin/message/custom/send')) {
+ return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ throw new Error(`unexpected wechat url: ${url}`);
+ },
+ });
+
+ const originalRandomUuid = crypto.randomUUID;
+ crypto.randomUUID = (() => {
+ const ids = ['req-skill-miss', 'req-skill-retry'];
+ return () => ids.shift() ?? 'req-skill-retry';
+ })();
+ try {
+ const result = await service.handleInboundMessage(inboundXml({ content: '请生成一个页面 public/hello-world.html' }), {
+ timestamp,
+ nonce,
+ signature: signatureFor(token, timestamp, nonce),
+ });
+ assert.equal(result.status, 200);
+ await result.task;
+ } finally {
+ crypto.randomUUID = originalRandomUuid;
+ }
+
+ assert.equal(routeCleared, true);
+ 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, /Hello World/);
+ assert.match(payload.text.content, /https:\/\/m\.tkmind\.cn\/MindSpace\/.+\/public\/hello-world\.html/);
+});
+
+test('wechat mp service recreates poisoned dedicated session after publish-success claim without html evidence', async () => {
+ const token = 'token';
+ const timestamp = '1710000000';
+ const nonce = 'nonce';
+ const wechatCalls = [];
+ let routeCleared = false;
+ let started = false;
+ const workspaceRoot = fs.mkdtempSync('/tmp/wechat-mp-publish-claim-');
+ const htmlPath = path.join(workspaceRoot, 'public', 'summer-breeze-journal.html');
+
+ const service = createWechatMpService({
+ config: {
+ enabled: true,
+ appId: 'wx123',
+ appSecret: 'secret',
+ token,
+ publicBaseUrl: 'https://m.tkmind.cn',
+ bindPath: '/auth/wechat/authorize?intent=login',
+ ackText: 'ack',
+ unsupportedText: 'unsupported',
+ unboundTextPrefix: '请先绑定',
+ progressDelayMs: 0,
+ },
+ userAuth: {
+ async findWechatUserByOpenid() {
+ return { userId: 'user-1', status: 'active', nickname: '毕升' };
+ },
+ async getWechatAgentRoute() {
+ return routeCleared ? null : { agentSessionId: 'session-1' };
+ },
+ async clearWechatAgentRoute() {
+ routeCleared = true;
+ },
+ async canUseChat() {
+ return { ok: true };
+ },
+ async resolveWorkingDir() {
+ return workspaceRoot;
+ },
+ async getAgentSessionPolicy() {
+ return {
+ enableContextMemory: false,
+ extensionOverrides: [
+ {
+ type: 'platform',
+ name: 'developer',
+ available_tools: ['write'],
+ },
+ ],
+ unrestricted: false,
+ };
+ },
+ async getUserPublishLayout() {
+ return { publishDir: workspaceRoot, displayName: 'John', username: 'john', slug: 'john', constraints: null };
+ },
+ async registerAgentSession() {},
+ async upsertWechatAgentRoute({ agentSessionId }) {
+ assert.equal(agentSessionId, 'session-2');
+ },
+ async billSessionUsage() {},
+ async recordWechatMpMessage() {
+ return { inserted: true };
+ },
+ async finishWechatMpMessage() {},
+ async insertWechatMpMessageDetail() {},
+ },
+ apiFetch: async (pathname, init = {}) => {
+ if (pathname === '/agent/start') {
+ started = true;
+ const body = JSON.parse(init.body);
+ assert.equal(body.working_dir, workspaceRoot);
+ return new Response(JSON.stringify({ id: 'session-2' }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ throw new Error(`unexpected api path: ${pathname}`);
+ },
+ sessionApiFetch: async (sessionId, pathname) => {
+ if (pathname === `/sessions/${sessionId}/extensions`) {
+ return new Response(JSON.stringify({ extensions: [{ name: 'developer', available_tools: ['write'] }] }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ if (pathname === '/agent/update_working_dir' || pathname === '/agent/update_session' || pathname === '/agent/restart') {
+ return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
+ }
+ if (pathname === '/agent/add_extension') {
+ return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
+ }
+ if (pathname === '/agent/harness_remember' || pathname === '/agent/harness_bootstrap') {
+ return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
+ }
+ if (pathname === `/sessions/${sessionId}`) {
+ return new Response(JSON.stringify({ working_dir: workspaceRoot }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ if (pathname === `/sessions/${sessionId}/reply`) {
+ if (sessionId === 'session-2') {
+ fs.mkdirSync(path.dirname(htmlPath), { recursive: true });
+ fs.writeFileSync(htmlPath, 'Summer');
+ }
+ return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
+ }
+ if (pathname === `/sessions/${sessionId}/events`) {
+ if (sessionId === 'session-1') {
+ return new Response(
+ [
+ 'data: {"type":"Message","request_id":"req-claim","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"好消息!m.tkmind.cn 其实是可以访问的,页面都已经成功发布了!以下是您的夏日主题页面:\\n\\n🌴 夏日主题页面已发布\\n\\nhttps://m.tkmind.cn/MindSpace/john/public/summer-breeze-journal.html"}]}}\n\n',
+ 'data: {"type":"Finish","request_id":"req-claim","token_state":{"inputTokens":1,"outputTokens":1}}\n\n',
+ ].join(''),
+ { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
+ );
+ }
+ return new Response(
+ [
+ 'data: {"type":"Message","request_id":"req-claim-retry","message":{"id":"assistant-skill","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"toolRequest","toolCall":{"value":{"name":"load_skill","arguments":{"name":"static-page-publish"}}}}]}}\n\n',
+ `data: {"type":"Message","request_id":"req-claim-retry","message":{"id":"assistant-tool","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"toolRequest","toolCall":{"value":{"name":"developer","arguments":{"action":"write","path":"${htmlPath.replace(/\\/g, '\\\\')}","content":"Summer"}}}}]}}\n\n`,
+ 'data: {"type":"Message","request_id":"req-claim-retry","message":{"id":"assistant-2","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"已完成"}]}}\n\n',
+ 'data: {"type":"Finish","request_id":"req-claim-retry","token_state":{"inputTokens":2,"outputTokens":2}}\n\n',
+ ].join(''),
+ { status: 200, headers: { 'Content-Type': 'text/event-stream' } },
+ );
+ }
+ throw new Error(`unexpected session api path: ${sessionId} ${pathname}`);
+ },
+ wechatFetch: async (url, init = {}) => {
+ wechatCalls.push([url, init.method ?? 'GET', init.body ?? null]);
+ if (String(url).includes('/cgi-bin/stable_token')) {
+ return new Response(JSON.stringify({ access_token: 'access-1', expires_in: 7200 }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ if (String(url).includes('/cgi-bin/message/custom/send')) {
+ return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+ }
+ throw new Error(`unexpected wechat url: ${url}`);
+ },
+ });
+
+ const originalRandomUuid = crypto.randomUUID;
+ crypto.randomUUID = (() => {
+ const ids = ['req-claim', 'req-claim-retry'];
+ return () => ids.shift() ?? 'req-claim-retry';
+ })();
+ try {
+ const result = await service.handleInboundMessage(
+ inboundXml({ content: '帮我生成一个夏日页面' }),
+ {
+ timestamp,
+ nonce,
+ signature: signatureFor(token, timestamp, nonce),
+ },
+ );
+ assert.equal(result.status, 200);
+ await result.task;
+ } finally {
+ crypto.randomUUID = originalRandomUuid;
+ }
+
+ assert.equal(routeCleared, true);
+ assert.equal(started, true);
+ 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, /summer-breeze-journal\.html/);
+ assert.match(payload.text.content, /已完成|页面生成未完成/);
+});
+
test('wechat mp service blocks schedule confirmation when no schedule item was written', async () => {
const token = 'token';
const timestamp = '1710000000';
@@ -2433,6 +3086,6 @@ test('wechat mp service exposes route status and can recreate route for bound us
assert.equal(recreated.ok, true);
assert.equal(recreated.route.agentSessionId, 'session-new');
assert.equal(calls.includes('cleared'), true);
- assert.equal(calls.some((item) => item === 'register:session-new'), false);
+ assert.equal(calls.some((item) => item === 'register:session-new'), true);
assert.equal(calls.includes('upserted'), true);
});