import assert from 'node:assert/strict'; import crypto from 'node:crypto'; import fs from 'node:fs'; import path from 'node:path'; import test from 'node:test'; import { buildWechatAgentPrompt, buildWechatTextReply, createWechatMpService, guardMissingPublicHtmlLinks, assertWechatAgentReplyIsSendable, findRecoverableWechatAgentErrorInReply, isRecoverableWechatAgentSessionError, isWechatAgentApiErrorText, sanitizeWechatAgentOutboundText, loadWechatMpConfig, maybeAttachPublishedHtmlLink, shouldRetryHtmlGenerationReply, shouldForceNewWechatAgentSession, splitWechatText, verifyWechatMpSignature, verifyWechatMpUrlChallenge, decryptWechatMpPayload, WECHAT_CUSTOMER_TEXT_MAX_BYTES, } from './wechat-mp.mjs'; function signatureFor(token, timestamp, nonce) { return crypto .createHash('sha1') .update([token, timestamp, nonce].sort().join('')) .digest('hex'); } function inboundXml({ toUserName = 'gh_test', fromUserName = 'openid-1', msgType = 'text', content = 'hello', event = '', extraFields = {}, } = {}) { const renderedExtras = Object.entries(extraFields) .map(([key, value]) => { if (value === '' || value === null || value === undefined) return ''; return typeof value === 'number' ? `<${key}>${value}` : `<${key}>`; }) .filter(Boolean) .join(''); return [ '', ``, ``, '1710000000', ``, content ? `` : '', event ? `` : '', renderedExtras, '10001', '', ].join(''); } function previewReadyPageHtml({ title = 'Page', subtitle = '测试页面' } = {}) { return `${title}
${'x'.repeat(600)}

TKMind · 智趣

`; } function jsonEscapedPreviewReadyPageHtml(options = {}) { return previewReadyPageHtml(options).replace(/\\/g, '\\\\').replace(/"/g, '\\"'); } function createBoundWechatService({ token = 'token', wechatFetch, sessionApiFetch, startAgentSession = null, userAuth = {}, config = {}, scheduleService = null, applySessionLlmProvider = null, submitSessionReply = null, }) { return createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', mediaPublicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', progressDelayMs: 0, maxImageBytes: 1024 * 1024, ...config, }, userAuth: { async findWechatUserByOpenid() { return { userId: 'user-1', status: 'active', nickname: '毕升' }; }, async getWechatAgentRoute() { return { agentSessionId: 'session-1' }; }, async clearWechatAgentRoute() {}, async canUseChat() { return { ok: true }; }, 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() {}, async recordWechatMpMessage() { return { inserted: true }; }, async finishWechatMpMessage() {}, async insertWechatMpMessageDetail() {}, ...userAuth, }, startAgentSession, sessionApiFetch, submitSessionReply, scheduleService, applySessionLlmProvider, wechatFetch, apiFetch: async () => new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }), }); } test('splitWechatText respects WeChat 2048-byte customer service limit', () => { const longChinese = '中'.repeat(900); const chunks = splitWechatText(longChinese); assert.ok(chunks.length > 1); for (const chunk of chunks) { assert.ok(Buffer.byteLength(chunk, 'utf8') <= WECHAT_CUSTOMER_TEXT_MAX_BYTES); } assert.equal(chunks.join(''), longChinese); }); test('Page Data requests always rotate away from an existing WeChat route', () => { assert.equal( shouldForceNewWechatAgentSession({ kind: 'chat.general' }, '做一个问卷,后台可以查看提交数据'), true, ); assert.equal(shouldForceNewWechatAgentSession({ kind: 'chat.general' }, '继续聊德川家康'), false); assert.equal(shouldForceNewWechatAgentSession({ kind: 'session.reset' }, '继续聊德川家康'), true); }); test('buildWechatAgentPrompt requires docx generation before html when Word download is requested', () => { const prompt = buildWechatAgentPrompt({ msgType: 'text', content: '帮我生成一个行业分布报告页面,提供链接word给我来下载', }); assert.match(prompt, /docx-generate/); assert.match(prompt, /public\/\*\.docx/); assert.match(prompt, /static-page-publish/); }); test('buildWechatAgentPrompt keeps normal html guidance without docx requirement for plain page requests', () => { const prompt = buildWechatAgentPrompt({ msgType: 'text', content: '帮我生成一个简单页面', }); assert.match(prompt, /static-page-publish/); assert.doesNotMatch(prompt, /docx-generate/); }); test('guardMissingPublicHtmlLinks blocks missing MindSpace public html links', async () => { const text = '页面已创建:\nhttps://m.tkmind.cn/MindSpace/a70ff537-8908-486e-9b6c-042e07cc25db/public/missing.html'; const guarded = await guardMissingPublicHtmlLinks(text, { linkExists: async () => false, }); assert.doesNotMatch(guarded, /https:\/\/g2\.tkmind\.cn\/MindSpace/); assert.match(guarded, /页面生成未完成/); assert.match(guarded, /missing\.html/); }); test('shouldRetryHtmlGenerationReply retries when reply link is missing on disk', () => { const intent = { agentText: '帮我生成一个 html 页面 public/test.html' }; const reply = { text: '[Test](https://m.tkmind.cn/MindSpace/user-1/public/test.html)', messages: [ { content: [ { type: 'toolRequest', toolCall: { value: { name: 'load_skill', arguments: { name: 'static-page-publish' } } }, }, ], }, ], }; assert.equal( shouldRetryHtmlGenerationReply({ reply, intent, confirmedArtifacts: [], hasValidLinkInReply: false, }), true, ); }); test('shouldRetryHtmlGenerationReply does not skip retry because unrelated recent html exists', () => { const intent = { agentText: '帮我做一个小页面吧' }; const reply = { text: '[Test](https://m.tkmind.cn/MindSpace/user-1/public/test.html)', messages: [ { content: [ { type: 'toolRequest', toolCall: { value: { name: 'load_skill', arguments: { name: 'static-page-publish' } } }, }, { type: 'toolRequest', toolCall: { value: { name: 'write_file', arguments: { path: 'public/other-page.html', content: '' }, }, }, }, ], }, ], }; assert.equal( shouldRetryHtmlGenerationReply({ reply, intent, confirmedArtifacts: [ { localPath: '/tmp/user-1/public/other-page.html', relativePath: 'public/other-page.html', url: 'https://m.tkmind.cn/MindSpace/user-1/public/other-page.html', }, ], hasValidLinkInReply: false, }), true, ); }); test('shouldRetryHtmlGenerationReply allows normal H5 agent text without fake page links', () => { const intent = { agentText: '帮我生成页面吧' }; const reply = { text: '请告诉我要生成什么主题和内容,我再继续。', messages: [ { content: [ { type: 'text', text: '请告诉我要生成什么主题和内容,我再继续。', }, ], }, ], }; assert.equal( shouldRetryHtmlGenerationReply({ reply, intent, confirmedArtifacts: [], hasValidLinkInReply: false, }), false, ); }); test('wechat mp service rejects blocked publish claims that did not use the H5 page skill', 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, /服务号页面技能|没有按 H5 里的页面技能/); assert.doesNotMatch(payload.text.content, /summer-breeze-journal\.html/); }); 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`; fs.writeFileSync(htmlPath, 'Hello'); const text = await maybeAttachPublishedHtmlLink( { text: '已完成', messages: [ { role: 'assistant', content: [ { type: 'toolRequest', toolCall: { value: { name: 'developer', arguments: { action: 'write', path: htmlPath, content: 'Hello', }, }, }, }, ], }, ], }, { workingDir: workspaceRoot, publicBaseUrl: 'https://m.tkmind.cn', }, ); assert.equal(fs.existsSync(`${workspaceRoot}/public/hello.html`), true); assert.match( text, /https:\/\/m\.tkmind\.cn\/MindSpace\/.+\/public\/hello\.html/, ); }); 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('maybeAttachPublishedHtmlLink can attach a verified existing public html link from provided artifacts', async () => { const workspaceRoot = fs.mkdtempSync('/tmp/wechat-mp-existing-public-'); const htmlPath = `${workspaceRoot}/public/thailand-guide.html`; fs.mkdirSync(`${workspaceRoot}/public`, { recursive: true }); fs.writeFileSync(htmlPath, previewReadyPageHtml({ title: 'Thailand', subtitle: '泰国旅行攻略' })); const text = await maybeAttachPublishedHtmlLink( { text: '页面已生成!点击下方链接查看:', messages: [], }, { workingDir: workspaceRoot, publicBaseUrl: 'https://m.tkmind.cn', artifacts: [ { localPath: htmlPath, relativePath: 'public/thailand-guide.html', url: `https://m.tkmind.cn/MindSpace/${path.basename(workspaceRoot)}/public/thailand-guide.html`, }, ], }, ); assert.match(text, /https:\/\/m\.tkmind\.cn\/MindSpace\/.+\/public\/thailand-guide\.html/); }); test('wechat mp service splits long agent replies into multiple customer messages', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const longReply = '答'.repeat(900); const wechatCalls = []; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', progressDelayMs: 0, unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', }, userAuth: { async findWechatUserByOpenid() { return { userId: 'user-1', status: 'active', nickname: '毕升' }; }, async getWechatAgentRoute() { return { agentSessionId: 'session-1' }; }, async clearWechatAgentRoute() {}, async canUseChat() { return { ok: true }; }, 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-long","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":${JSON.stringify(longReply)}}]}}\n\n`, 'data: {"type":"Finish","request_id":"req-long","token_state":{"inputTokens":1,"outputTokens":2}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === '/sessions/session-1/reply') { 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, 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')) { const payload = JSON.parse(init.body); assert.ok(Buffer.byteLength(payload.text.content, 'utf8') <= WECHAT_CUSTOMER_TEXT_MAX_BYTES); 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-long'; 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; } const sendCalls = wechatCalls.filter(([url]) => String(url).includes('/cgi-bin/message/custom/send')); assert.ok(sendCalls.length > 1); const combined = sendCalls .map(([, , body]) => JSON.parse(body).text.content) .join(''); assert.equal(combined, longReply); }); test('wechat mp service surfaces wechat customer-service errcode and errmsg', async () => { const service = createBoundWechatService({ userAuth: { async getWechatOpenidForUser() { return 'openid-1'; }, }, 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' }, }); } return new Response(JSON.stringify({ errcode: 45047, errmsg: 'out of response count limit rid: test-rid' }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); }, }); await assert.rejects( service.sendTextToUser('user-1', 'hello'), /errcode=45047.*out of response count limit/, ); }); test('wechat mp service strips markdown emphasis around outbound links', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const wechatCalls = []; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', progressDelayMs: 0, unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', }, userAuth: { async findWechatUserByOpenid() { return { userId: 'user-1', status: 'active', nickname: '毕升' }; }, async getWechatAgentRoute() { return { agentSessionId: 'session-1' }; }, async clearWechatAgentRoute() {}, async canUseChat() { return { ok: true }; }, 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) => { assert.equal(sessionId, 'session-1'); if (pathname === '/sessions/session-1/events') { return new Response( [ `data: {"type":"Message","request_id":"req-link-md","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"👉 **跑步路书**\\nhttps://example.com/running-route.html**"}]}}\n\n`, 'data: {"type":"Finish","request_id":"req-link-md","token_state":{"inputTokens":1,"outputTokens":2}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === '/sessions/session-1/reply') { 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, 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}`); }, linkExists: async (urlText) => { if (!String(urlText).includes('/summer-breeze-journal.html')) return false; return fs.existsSync(htmlPath); }, }); const originalRandomUuid = crypto.randomUUID; crypto.randomUUID = () => 'req-link-md'; 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; } const sendCall = wechatCalls.find(([url]) => String(url).includes('/cgi-bin/message/custom/send')); const payload = JSON.parse(sendCall[2]); assert.equal(payload.text.content.includes('**'), false); assert.equal(payload.text.content.includes('https://example.com/running-route.html**'), false); assert.match(payload.text.content, /https:\/\/example\.com\/running-route\.html/); }); test('wechat mp service does not send stale page links from unscoped conversation snapshots', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const wechatCalls = []; const service = createBoundWechatService({ sessionApiFetch: async (sessionId, pathname) => { assert.equal(sessionId, 'session-1'); if (pathname === '/sessions/session-1/events') { return new Response( [ 'data: {"type":"UpdateConversation","conversation":[{"id":"user-old","role":"user","metadata":{"userVisible":true},"content":[{"type":"text","text":"帮我做餐厅推荐"}]},{"id":"assistant-old","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"页面已生成!餐厅推荐\\nhttps://g2.tkmind.cn/MindSpace/old/public/restaurant.html"}]}]}\n\n', 'data: {"type":"Finish","request_id":"req-stale","token_state":{"inputTokens":1,"outputTokens":2}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === '/sessions/session-1/reply') { 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, 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}`); }, linkExists: async (urlText) => { if (!String(urlText).includes('/summer-breeze-journal.html')) return false; return fs.existsSync(htmlPath); }, }); const originalRandomUuid = crypto.randomUUID; crypto.randomUUID = () => 'req-stale'; 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; } const sendCalls = wechatCalls.filter(([url]) => String(url).includes('/cgi-bin/message/custom/send')); assert.equal(sendCalls.length, 1); const payload = JSON.parse(sendCalls[0][2]); assert.doesNotMatch(payload.text.content, /restaurant\.html/); assert.match(payload.text.content, /本轮未收到可发送的新回复/); }); test('loadWechatMpConfig requires full config and enable flag', () => { const config = loadWechatMpConfig({ H5_WECHAT_MP_ENABLED: '1', H5_WECHAT_MP_APP_ID: 'wx123', H5_WECHAT_MP_APP_SECRET: 'secret', H5_WECHAT_MP_TOKEN: 'token', H5_PUBLIC_BASE_URL: 'https://example.com', }); assert.equal(config.enabled, true); assert.equal(config.bindPath, '/auth/wechat/authorize?intent=login'); }); test('verifyWechatMpSignature accepts valid signature', () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; assert.equal( verifyWechatMpSignature({ token, timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }), true, ); }); test('verifyWechatMpUrlChallenge supports aes mode echostr', () => { const token = 'memindprodmp'; const appId = 'wxaa5f3cbb9ee009c2'; const encodingAesKey = 'oovBs3npy6NdflERl0LObynzVwH8UgwtCKrfngwfBod'; const timestamp = '1710000000'; const nonce = 'nonce123'; const plainEcho = '77889900'; const key = Buffer.from(`${encodingAesKey}=`, 'base64'); const iv = key.subarray(0, 16); const random16 = Buffer.alloc(16, 0x61); const msgBuf = Buffer.from(plainEcho, 'utf8'); const appIdBuf = Buffer.from(appId, 'utf8'); const lenBuf = Buffer.alloc(4); lenBuf.writeUInt32BE(msgBuf.length, 0); const raw = Buffer.concat([random16, lenBuf, msgBuf, appIdBuf]); const pad = 32 - (raw.length % 32); const padded = Buffer.concat([raw, Buffer.alloc(pad, pad)]); const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); cipher.setAutoPadding(false); const encrypted = Buffer.concat([cipher.update(padded), cipher.final()]).toString('base64'); const msgSignature = crypto .createHash('sha1') .update([token, timestamp, nonce, encrypted].sort().join('')) .digest('hex'); const result = verifyWechatMpUrlChallenge( { encrypt_type: 'aes', msg_signature: msgSignature, timestamp, nonce, echostr: encrypted, }, { token, appId, encodingAesKey }, ); assert.equal(result.ok, true); assert.equal(result.body, plainEcho); }); test('buildWechatTextReply includes both parties and content', () => { const xml = buildWechatTextReply({ toUserName: 'openid-1', fromUserName: 'gh_test', content: '收到', createTime: 123, }); assert.match(xml, /<\/ToUserName>/); assert.match(xml, /<\/Content>/); }); test('wechat mp service prompts binding for unknown openid', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', }, userAuth: { async findWechatUserByOpenid() { return null; }, }, apiFetch: async () => new Response('{}', { status: 200 }), }); const result = await service.handleInboundMessage(inboundXml(), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }); assert.equal(result.status, 200); assert.match(result.body, /请先绑定/); assert.match(result.body, /https:\/\/example.com\/auth\/wechat\/authorize\?intent=login/); }); test('wechat mp service routes text to dedicated session and sends customer service reply', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const apiCalls = []; const wechatCalls = []; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', }, userAuth: { async findWechatUserByOpenid() { return { userId: 'user-1', status: 'active', nickname: '毕升' }; }, async getWechatAgentRoute() { return { agentSessionId: 'session-1' }; }, async clearWechatAgentRoute() {}, async canUseChat() { return { ok: true }; }, 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(userId, sessionId, tokenState, requestId) { apiCalls.push(['bill', userId, sessionId, tokenState, requestId]); }, }, apiFetch: async (pathname, init = {}) => { apiCalls.push(['fallback', pathname, init.method ?? 'GET']); return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); }, sessionApiFetch: async (sessionId, pathname, init = {}) => { assert.equal(sessionId, 'session-1'); apiCalls.push([pathname, init.method ?? 'GET']); 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":"wx_abcd1234,页面已创建完毕!\\n[标题](https://example.com/page.html)\\n\\n| 项 | 值 |\\n| --- | --- |\\n| A | B |"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-1","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, /用户消息:帮我做个页面/); 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' } }); } if (pathname === '/agent/harness_remember') { const body = JSON.parse(init.body); assert.equal(body.sessionId, 'session-1'); assert.match(body.content, /当前服务号用户名称/); return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); } if (pathname === '/agent/session/reconcile') { const body = JSON.parse(init.body); assert.equal(body.user_context.displayName, '毕升'); assert.equal(body.user_context.username, '毕升'); assert.equal(body.user_context.slug, null); return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); } if (pathname === '/agent/harness_bootstrap') { const body = JSON.parse(init.body); assert.equal(body.sessionId, 'session-1'); assert.equal(body.force, false); return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); } throw new Error(`unexpected api path: ${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')) { const payload = JSON.parse(init.body); assert.equal(payload.touser, 'openid-1'); assert.match(payload.text.content, /页面已创建完毕/); assert.doesNotMatch(payload.text.content, /^wx_/); assert.match(payload.text.content, /^毕升,页面已创建完毕/); assert.doesNotMatch(payload.text.content, /\[标题\]\(/); assert.match(payload.text.content, /标题\nhttps:\/\/example.com\/page.html/); assert.match(payload.text.content, /项:A;值:B/); return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); } 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; let uuidCalls = 0; crypto.randomUUID = () => { uuidCalls += 1; return uuidCalls === 1 ? 'req-1' : `uuid-${uuidCalls}`; }; try { const result = await service.handleInboundMessage(inboundXml({ content: '帮我做个页面' }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }); assert.equal(result.status, 200); assert.match(result.body, //); await result.task; } finally { crypto.randomUUID = originalRandomUuid; } assert.equal( apiCalls.some(([pathname]) => pathname === '/sessions/session-1/reply'), true, ); assert.equal( apiCalls.some(([kind]) => kind === 'fallback'), false, ); assert.equal( wechatCalls.some(([url]) => String(url).includes('/cgi-bin/message/custom/send')), true, ); }); test('wechat mp service rewrites internal wx username mentions that appear mid-message', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const sentPayloads = []; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', progressDelayMs: 0, }, userAuth: { async findWechatUserByOpenid() { return { userId: 'user-1', status: 'active', nickname: 'Lumi' }; }, async getWechatAgentRoute() { return { agentSessionId: 'session-1' }; }, async clearWechatAgentRoute() {}, async canUseChat() { return { ok: true }; }, async resolveWorkingDir() { return '/tmp/user-1'; }, async getAgentSessionPolicy() { return { enableContextMemory: false, extensionOverrides: [], unrestricted: true }; }, async getUserPublishLayout() { return { displayName: 'Lumi', username: 'wx_ul610et8', slug: 'wx_ul610et8', 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-mid","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"LumiMind 这个感觉很好。\\n\\nwx_ul610et8,这个感觉对了。\\n好的,wx_ul610et8,为你写了一首。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-mid","token_state":{"inputTokens":1,"outputTokens":2}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === '/sessions/session-1/reply') { 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, init = {}) => { 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')) { sentPayloads.push(JSON.parse(init.body)); return new Response(JSON.stringify({ errcode: 0, errmsg: 'ok' }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); } 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; crypto.randomUUID = () => 'req-mid'; try { const result = await service.handleInboundMessage( inboundXml({ msgType: 'text', content: '这个名字怎么样' }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }, ); await result.task; } finally { crypto.randomUUID = originalRandomUuid; } assert.equal(sentPayloads.length, 1); assert.doesNotMatch(sentPayloads[0].text.content, /wx_ul610et8/); assert.match(sentPayloads[0].text.content, /\nLumi,这个感觉对了。/); assert.match(sentPayloads[0].text.content, /好的,Lumi,为你写了一首。/); }); test('wechat mp service applies provider before forwarding to dedicated session', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const appliedSessions = []; const service = createBoundWechatService({ token, applySessionLlmProvider: async (sessionId) => { appliedSessions.push(sessionId); return { ok: true, providerId: 'custom_tkmind_relay_deepseek', model: 'deepseek-chat' }; }, sessionApiFetch: async (sessionId, pathname) => { assert.equal(sessionId, 'session-1'); if (pathname === '/sessions/session-1/events') { return new Response( [ 'data: {"type":"Message","request_id":"req-provider","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"收到。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-provider","token_state":{"inputTokens":1,"outputTokens":2}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === '/sessions/session-1/reply') { 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-provider'; 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.deepEqual(appliedSessions, ['session-1']); }); test('wechat mp service rotates an idle dedicated session before replying', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const clearedRoutes = []; const upsertedSessions = []; const sessionApiCalls = []; let routeCleared = false; const service = createBoundWechatService({ token, config: { sessionIdleRotateMs: 1000, }, startAgentSession: async () => ({ id: 'session-2' }), sessionApiFetch: async (sessionId, pathname, init = {}) => { sessionApiCalls.push([sessionId, pathname, init.body ?? null]); assert.equal(sessionId, 'session-2'); if (pathname === '/sessions/session-2/events') { return new Response( [ 'data: {"type":"Message","request_id":"req-rotate","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"收到,新的会话已接上。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-rotate","token_state":{"inputTokens":1,"outputTokens":2}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === '/sessions/session-2/reply') { 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}`); }, userAuth: { async getWechatAgentRoute() { if (routeCleared) return null; return { agentSessionId: 'session-1', status: 'active', updatedAt: Date.now() - 60_000, }; }, async clearWechatAgentRoute(appId, openid) { routeCleared = true; clearedRoutes.push([appId, openid]); }, async upsertWechatAgentRoute({ agentSessionId }) { upsertedSessions.push(agentSessionId); }, async finishWechatMpMessage() {}, }, }); const originalRandomUuid = crypto.randomUUID; crypto.randomUUID = () => 'req-rotate'; 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.deepEqual(clearedRoutes, [['wx123', 'openid-1']]); assert.deepEqual(upsertedSessions, ['session-2']); assert.ok(sessionApiCalls.some(([sessionId, pathname]) => sessionId === 'session-2' && pathname === '/sessions/session-2/reply')); }); test('wechat mp service rotates a dedicated session after the message-count threshold', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; let routeCleared = false; const countedSessions = []; const service = createBoundWechatService({ token, config: { sessionIdleRotateMs: 0, sessionMessageRotateCount: 2, }, startAgentSession: async () => ({ id: 'session-2' }), sessionApiFetch: async (sessionId, pathname) => { assert.equal(sessionId, 'session-2'); if (pathname === '/sessions/session-2/events') { return new Response( [ 'data: {"type":"Message","request_id":"req-count-rotate","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"已换新会话。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-count-rotate","token_state":{"inputTokens":1,"outputTokens":2}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === '/sessions/session-2/reply') { 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}`); }, userAuth: { async getWechatAgentRoute() { if (routeCleared) return null; return { agentSessionId: 'session-1', status: 'active', updatedAt: Date.now(), }; }, async countWechatAgentSessionMessages({ agentSessionId }) { countedSessions.push(agentSessionId); return 2; }, async clearWechatAgentRoute() { routeCleared = true; }, async upsertWechatAgentRoute() {}, async finishWechatMpMessage() {}, }, }); const originalRandomUuid = crypto.randomUUID; crypto.randomUUID = () => 'req-count-rotate'; 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.deepEqual(countedSessions, ['session-1']); assert.equal(routeCleared, true); }); test('wechat mp service rotates a dedicated session when snapshot message count exceeds threshold', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; let routeCleared = false; const service = createBoundWechatService({ token, config: { sessionIdleRotateMs: 0, sessionMessageRotateCount: 200, }, startAgentSession: async () => ({ id: 'session-2' }), sessionApiFetch: async (sessionId, pathname) => { assert.equal(sessionId, 'session-2'); if (pathname === '/sessions/session-2/events') { return new Response( [ 'data: {"type":"Message","request_id":"req-snapshot-rotate","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"已换新会话。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-snapshot-rotate","token_state":{"inputTokens":1,"outputTokens":2}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === '/sessions/session-2/reply') { 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}`); }, userAuth: { async getWechatAgentRoute() { if (routeCleared) return null; return { agentSessionId: 'session-1', status: 'active', updatedAt: Date.now(), }; }, async countWechatAgentSessionMessages() { return 3; }, async getWechatAgentSessionSnapshotMessageCount() { return 616; }, async clearWechatAgentRoute() { routeCleared = true; }, async upsertWechatAgentRoute() {}, async finishWechatMpMessage() {}, }, }); const originalRandomUuid = crypto.randomUUID; crypto.randomUUID = () => 'req-snapshot-rotate'; 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); }); test('wechat mp service skips repeated user-context bootstrap for the same session', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const bootstrapBodies = []; const service = createBoundWechatService({ token, 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-bootstrap","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"收到。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-bootstrap","token_state":{"inputTokens":1,"outputTokens":2}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === '/sessions/session-1/reply') { return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); } if (pathname === '/agent/harness_remember') { return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); } if (pathname === '/agent/harness_bootstrap') { bootstrapBodies.push(JSON.parse(String(init.body ?? '{}'))); 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-bootstrap'; try { for (const content of ['第一条', '第二条']) { 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.deepEqual(bootstrapBodies, [{ sessionId: 'session-1', force: false }]); }); 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'; 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-2","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"收到,我先帮你处理。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-2","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, /加载 `schedule-assistant` skill/); assert.match(body.user_message.content[0].text, /schedule_create_item/); assert.match(body.user_message.content[0].text, /sourceMessageId: 10001/); 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-2'; 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; } }); test('wechat mp service reconciles existing dedicated session before reply', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const calls = []; let extensionsReadCount = 0; const workingDir = '/Users/john/Project/Memind/MindSpace/user-1'; const extensionConfig = { type: 'stdio', name: 'sandbox-fs', cmd: '/usr/local/bin/node', args: ['/Users/john/Project/Memind/mindspace-sandbox-mcp.mjs', workingDir], envs: { SANDBOX_ROOT: workingDir }, available_tools: ['list_dir'], }; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', }, userAuth: { async findWechatUserByOpenid() { return { userId: 'user-1', status: 'active', nickname: '毕升' }; }, async getWechatAgentRoute() { return { agentSessionId: 'session-1' }; }, async clearWechatAgentRoute() {}, async canUseChat() { return { ok: true }; }, async resolveWorkingDir() { return workingDir; }, async getAgentSessionPolicy() { return { enableContextMemory: false, extensionOverrides: [extensionConfig], unrestricted: false, gooseMode: 'auto', }; }, async getUserPublishLayout() { return { displayName: 'John', username: 'john', slug: 'john', constraints: 'base' }; }, async registerAgentSession() {}, async upsertWechatAgentRoute() {}, async billSessionUsage() {}, }, apiFetch: async (pathname, init = {}) => { calls.push(['fallback', pathname, init.method ?? 'GET']); return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); }, sessionApiFetch: async (sessionId, pathname, init = {}) => { assert.equal(sessionId, 'session-1'); calls.push([pathname, init.method ?? 'GET']); if (pathname === '/sessions/session-1') { return new Response(JSON.stringify({ working_dir: '/root/tkmind_go/ui/h5/MindSpace/user-1' }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); } if (pathname === '/agent/update_working_dir') { const body = JSON.parse(init.body); assert.equal(body.session_id, 'session-1'); assert.equal(body.working_dir, workingDir); return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); } if (pathname === '/agent/update_session') { const body = JSON.parse(init.body); assert.equal(body.session_id, 'session-1'); assert.equal(body.goose_mode, 'auto'); return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); } if (pathname === '/sessions/session-1/extensions') { extensionsReadCount += 1; const extensions = extensionsReadCount > 1 ? [{ name: 'sandbox-fs', available_tools: ['list_dir'] }] : []; return new Response(JSON.stringify({ extensions }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); } if (pathname === '/agent/add_extension') { const body = JSON.parse(init.body); assert.equal(body.session_id, 'session-1'); assert.equal(body.config.name, 'sandbox-fs'); assert.equal(body.config.args[1], workingDir); return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); } if (pathname === '/agent/restart') { return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); } if (pathname === '/agent/harness_remember') { return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); } if (pathname === '/agent/harness_bootstrap') { return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); } 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":"收到"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-1","token_state":{"inputTokens":1,"outputTokens":1}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === '/sessions/session-1/reply') { return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); } throw new Error(`unexpected api path: ${pathname}`); }, wechatFetch: async (url, init = {}) => { 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-1'; try { const result = await service.handleInboundMessage(inboundXml({ content: '继续' }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }); assert.equal(result.status, 200); await result.task; assert.equal(calls.some(([pathname]) => pathname === '/agent/update_working_dir'), true); assert.equal(calls.some(([pathname]) => pathname === '/agent/add_extension'), true); assert.equal(calls.some(([pathname]) => pathname === '/sessions/session-1/reply'), true); } finally { crypto.randomUUID = originalRandomUuid; } }); test('wechat mp service recreates poisoned dedicated session after bare completion on html generation', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const wechatCalls = []; const workspaceRoot = fs.mkdtempSync('/tmp/wechat-mp-poisoned-session-'); const htmlPath = `${workspaceRoot}/hello.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({ appId, openid, userId, agentSessionId }) { assert.equal(appId, 'wx123'); assert.equal(openid, 'openid-1'); assert.equal(userId, 'user-1'); 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, init = {}) => { 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.writeFileSync(htmlPath, previewReadyPageHtml({ title: 'Hello', subtitle: '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-poisoned","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"已完成"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-poisoned","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-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":"${jsonEscapedPreviewReadyPageHtml({ title: 'Hello', subtitle: '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', ].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}`); }, linkExists: async (urlText) => { if (!String(urlText).includes('/summer-breeze-journal.html')) return false; return fs.existsSync(htmlPath); }, }); const originalRandomUuid = crypto.randomUUID; crypto.randomUUID = (() => { const ids = ['req-poisoned', 'req-poisoned-retry']; return () => ids.shift() ?? 'req-poisoned-retry'; })(); try { const result = await service.handleInboundMessage( inboundXml({ content: '帮我生成一个简单的 hello 页面吧' }), { 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, /已完成/); assert.match(payload.text.content, /页面生成未完成|hello\.html/); }); test('wechat mp service forwards html link when write_file created a real page without 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, previewReadyPageHtml({ title: 'Hello', subtitle: '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":"${jsonEscapedPreviewReadyPageHtml({ title: 'Hello World', subtitle: 'Hello World 页面' })}"}}}}]}}\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":"${jsonEscapedPreviewReadyPageHtml({ title: 'Hello World', subtitle: 'Hello World 页面' })}"}}}}]}}\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, false); assert.equal(started, 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, /hello-world\.html/); assert.match(payload.text.content, /https:\/\/m\.tkmind\.cn\/MindSpace\/.+\/public\/hello-world\.html/); }); test('wechat mp service trusts an existing requested public html file when the assistant only returns a title', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const wechatCalls = []; const workspaceRoot = fs.mkdtempSync('/tmp/wechat-mp-existing-requested-'); const htmlPath = path.join(workspaceRoot, 'public', 'thailand-guide.html'); fs.mkdirSync(path.dirname(htmlPath), { recursive: true }); fs.writeFileSync(htmlPath, previewReadyPageHtml({ title: 'Thailand', subtitle: '泰国旅行攻略' })); 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 { agentSessionId: 'session-1' }; }, async clearWechatAgentRoute() {}, async canUseChat() { return { ok: true }; }, async resolveWorkingDir() { return workspaceRoot; }, async getAgentSessionPolicy() { return { enableContextMemory: false, extensionOverrides: [], unrestricted: true }; }, async getUserPublishLayout() { return { publishDir: workspaceRoot, displayName: 'John', username: 'john', slug: 'john', constraints: null }; }, async billSessionUsage() {}, async recordWechatMpMessage() { return { inserted: true }; }, async finishWechatMpMessage() {}, async insertWechatMpMessageDetail() {}, }, sessionApiFetch: async (sessionId, pathname) => { if (pathname === `/sessions/${sessionId}/events`) { return new Response( [ 'data: {"type":"Message","request_id":"req-existing","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-existing","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"页面已生成!点击下方链接查看:\\n\\n🌴 泰国旅行攻略 · 说走就走"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-existing","token_state":{"inputTokens":1,"outputTokens":1}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === `/sessions/${sessionId}/reply`) { 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 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 = () => 'req-existing'; try { const result = await service.handleInboundMessage(inboundXml({ content: '请生成 thailand-guide.html 页面' }), { timestamp, nonce, signature: signatureFor(token, timestamp, 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.match(payload.text.content, /https:\/\/m\.tkmind\.cn\/MindSpace\/.+\/public\/thailand-guide\.html/); assert.doesNotMatch(payload.text.content, /页面生成未完成/); }); test('wechat mp service attaches the newly generated page link even when no html tool call is streamed', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const wechatCalls = []; const workspaceRoot = fs.mkdtempSync('/tmp/wechat-mp-recent-public-'); 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 { agentSessionId: 'session-1' }; }, async clearWechatAgentRoute() {}, async canUseChat() { return { ok: true }; }, async resolveWorkingDir() { return workspaceRoot; }, async getAgentSessionPolicy() { return { enableContextMemory: false, extensionOverrides: [], unrestricted: true }; }, async getUserPublishLayout() { return { publishDir: workspaceRoot, displayName: 'John', username: 'john', slug: 'john', constraints: null }; }, async billSessionUsage() {}, async recordWechatMpMessage() { return { inserted: true }; }, async finishWechatMpMessage() {}, async insertWechatMpMessageDetail() {}, }, sessionApiFetch: async (sessionId, pathname) => { if (pathname === `/sessions/${sessionId}/events`) { return new Response( [ 'data: {"type":"Message","request_id":"req-recent","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-recent","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"页面已生成!点击下方链接查看:\\n\\n🌴 夏日主题页面"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-recent","token_state":{"inputTokens":1,"outputTokens":1}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === `/sessions/${sessionId}/reply`) { fs.mkdirSync(path.dirname(htmlPath), { recursive: true }); fs.writeFileSync(htmlPath, previewReadyPageHtml({ title: 'Summer', subtitle: '夏日主题页面' })); 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 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 = () => 'req-recent'; 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; } const sendCall = wechatCalls.find(([url]) => String(url).includes('/cgi-bin/message/custom/send')); const payload = JSON.parse(sendCall[2]); assert.match(payload.text.content, /https:\/\/m\.tkmind\.cn\/MindSpace\/.+\/public\/summer-breeze-journal\.html/); assert.doesNotMatch(payload.text.content, /页面生成未完成|旧指令污染|稍后重试/); }); test('wechat mp service does not trust unrelated recent html artifacts when reply links a missing file', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const wechatCalls = []; const workspaceRoot = fs.mkdtempSync('/tmp/wechat-mp-recent-mismatch-'); const unrelatedHtmlPath = 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 { agentSessionId: 'session-1' }; }, async clearWechatAgentRoute() {}, async canUseChat() { return { ok: true }; }, async resolveWorkingDir() { return workspaceRoot; }, async getAgentSessionPolicy() { return { enableContextMemory: false, extensionOverrides: [], unrestricted: true }; }, async getUserPublishLayout() { return { publishDir: workspaceRoot, displayName: 'John', username: 'john', slug: 'john', constraints: null }; }, async billSessionUsage() {}, async recordWechatMpMessage() { return { inserted: true }; }, async finishWechatMpMessage() {}, async insertWechatMpMessageDetail() {}, }, sessionApiFetch: async (sessionId, pathname) => { if (pathname === `/sessions/${sessionId}/events`) { return new Response( [ 'data: {"type":"Message","request_id":"req-recent-mismatch","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-recent-mismatch","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"[Hello](https://m.tkmind.cn/MindSpace/user-1/public/hello.html)"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-recent-mismatch","token_state":{"inputTokens":1,"outputTokens":1}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === `/sessions/${sessionId}/reply`) { fs.mkdirSync(path.dirname(unrelatedHtmlPath), { recursive: true }); fs.writeFileSync(unrelatedHtmlPath, 'Summer'); 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 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 = () => 'req-recent-mismatch'; try { const result = await service.handleInboundMessage(inboundXml({ content: '帮我生成一个简单的 hello 页面吧' }), { timestamp, nonce, signature: signatureFor(token, timestamp, 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, /https:\/\/m\.tkmind\.cn\/MindSpace\/.+\/public\/hello\.html/); assert.doesNotMatch(payload.text.content, /summer-breeze-journal\.html/); assert.match(payload.text.content, /服务号页面技能|没有按 H5 里的页面技能|页面生成未完成/); }); test('wechat mp service forwards H5 agent text when page generation produced no file or fake link', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const wechatCalls = []; const workspaceRoot = fs.mkdtempSync('/tmp/wechat-mp-fallback-page-'); 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 { agentSessionId: 'session-1' }; }, async clearWechatAgentRoute() {}, async canUseChat() { return { ok: true }; }, async resolveWorkingDir() { return workspaceRoot; }, async getAgentSessionPolicy() { return { enableContextMemory: false, extensionOverrides: [], unrestricted: true }; }, async getUserPublishLayout() { return { publishDir: workspaceRoot, displayName: 'John', username: 'john', slug: 'john', constraints: null }; }, async billSessionUsage() {}, async recordWechatMpMessage() { return { inserted: true }; }, async finishWechatMpMessage() {}, async insertWechatMpMessageDetail() {}, }, sessionApiFetch: async (sessionId, pathname) => { if (pathname === `/sessions/${sessionId}/events`) { return new Response( [ 'data: {"type":"Message","request_id":"req-fallback","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"🌴 泰国简易攻略"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-fallback","token_state":{"inputTokens":1,"outputTokens":1}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === `/sessions/${sessionId}/reply`) { 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 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 = () => 'req-fallback'; 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; } const htmlPath = path.join(workspaceRoot, 'public', 'thailand-guide.html'); 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, /https:\/\/m\.tkmind\.cn\/MindSpace\/.+\/public\/thailand-guide\.html/); }); test('isRecoverableWechatAgentSessionError detects poisoned tool_calls history', () => { assert.equal( isRecoverableWechatAgentSessionError( "Request failed: Bad request (400): An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. (insufficient tool messages following tool_calls message).", ), true, ); assert.equal(isRecoverableWechatAgentSessionError('stale_session_poisoned_completion'), true); assert.equal(isRecoverableWechatAgentSessionError('无权访问该会话'), true); assert.equal( isRecoverableWechatAgentSessionError('Session already has an active request. Cancel it first.'), true, ); assert.equal(isRecoverableWechatAgentSessionError('network timeout'), false); }); test('findRecoverableWechatAgentErrorInReply scans all assistant messages', () => { const toolCallsError = "Request failed: Bad request (400): An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. (insufficient tool messages following tool_calls message)."; const reply = { text: '唐,好的,请稍等片刻。', messages: [ { id: 'assistant-1', role: 'assistant', content: [{ type: 'text', text: '唐,好的,请稍等片刻。' }], }, { id: 'assistant-2', role: 'assistant', content: [{ type: 'text', text: `Ran into this error:\n${toolCallsError}` }], }, ], }; assert.match(findRecoverableWechatAgentErrorInReply(reply), /tool_calls/); assert.throws(() => assertWechatAgentReplyIsSendable(reply), /tool_calls/); }); test('sanitizeWechatAgentOutboundText replaces raw api errors with friendly text', () => { const raw = "Ran into this error:\nRequest failed: Bad request (400): An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'."; assert.match(sanitizeWechatAgentOutboundText(raw), /切换到新会话/); assert.equal(isWechatAgentApiErrorText(raw), true); assert.match( sanitizeWechatAgentOutboundText('唐,好的。\n\nRan into this error:\nRequest failed: Bad request (400): tool_calls'), /唐,好的/, ); assert.doesNotMatch( sanitizeWechatAgentOutboundText('唐,好的。\n\nRan into this error:\nRequest failed: Bad request (400): tool_calls'), /Bad request/, ); }); test('assertWechatAgentReplyIsSendable rejects image inspection placeholders', () => { assert.throws( () => assertWechatAgentReplyIsSendable({ text: 'Let me look at the image first.' }), /wechat_agent_incomplete_reply/, ); assert.throws( () => assertWechatAgentReplyIsSendable({ text: '我先看一下这张图片。' }), /wechat_agent_incomplete_reply/, ); }); test('wechat mp service recreates dedicated session when tool_calls error arrives via Finish assistant text', 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-tool-calls-finish-'); const toolCallsError = "Request failed: Bad request (400): An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. (insufficient tool messages following tool_calls message)."; 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: '唐', username: 'wx_ul610et8', slug: 'wx_ul610et8', 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; 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, init = {}) => { 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' || pathname === '/agent/add_extension' || 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`) { 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-tool-finish","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"唐,好的,请稍等片刻。"}]}}\n\n', `data: {"type":"Message","request_id":"req-tool-finish","message":{"id":"assistant-2","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"Ran into this error:\\n${toolCallsError.replace(/"/g, '\\"')}"}]}}\n\n`, 'data: {"type":"Finish","request_id":"req-tool-finish","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-tool-retry","message":{"id":"assistant-3","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"已恢复,可以继续对话。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-tool-retry","token_state":{"inputTokens":1,"outputTokens":1}}\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-tool-finish', 'req-tool-retry']; return () => ids.shift() ?? 'req-tool-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.match(payload.text.content, /已恢复,可以继续对话/); assert.doesNotMatch(payload.text.content, /Bad request/); assert.doesNotMatch(payload.text.content, /tool_calls/); }); test('wechat mp service recreates dedicated session after poisoned tool_calls history', 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-tool-calls-poison-'); 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: '唐', username: 'wx_ul610et8', slug: 'wx_ul610et8', 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; 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, init = {}) => { 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' || pathname === '/agent/add_extension' || 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`) { return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); } if (pathname === `/sessions/${sessionId}/events`) { if (sessionId === 'session-1') { return new Response( [ 'data: {"type":"Error","request_id":"req-tool-poison","error":"Request failed: Bad request (400): An assistant message with \'tool_calls\' must be followed by tool messages responding to each \'tool_call_id\'. (insufficient tool messages following tool_calls message)."}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } return new Response( [ 'data: {"type":"Message","request_id":"req-tool-retry","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"已恢复,可以继续对话。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-tool-retry","token_state":{"inputTokens":1,"outputTokens":1}}\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-tool-poison', 'req-tool-retry']; return () => ids.shift() ?? 'req-tool-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.match(payload.text.content, /已恢复,可以继续对话/); }); test('wechat mp service retries poisoned publish claims before forwarding H5 retry text', 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 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`) { 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-2","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"🌴 夏日主题页面"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-claim-retry","token_state":{"inputTokens":1,"outputTokens":1}}\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, /🌴 夏日主题页面/); assert.doesNotMatch(payload.text.content, /没有按 H5 里的页面技能真正生成成功/); assert.doesNotMatch(payload.text.content, /summer-breeze-journal\.html/); }); test('wechat mp service blocks schedule confirmation when no schedule item was written', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const outbound = []; const service = createBoundWechatService({ token, scheduleService: { async listItemsBySourceMessage(input) { assert.equal(input.userId, 'user-1'); assert.equal(input.sourceMessageId, '10001'); return []; }, }, 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-guard","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"已经设置好了!明早 5:30 提醒起床,6:00 去跑步。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-guard","token_state":{"inputTokens":1,"outputTokens":2}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === '/sessions/session-1/reply') { 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, init = {}) => { 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')) { outbound.push(JSON.parse(init.body).text.content); 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-guard'; 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(outbound.length, 1); assert.match(outbound[0], /没有确认到待办\/提醒已经写入系统/); assert.doesNotMatch(outbound[0], /已经设置好了/); }); test('wechat mp service creates daily todo digest without routing to agent', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const apiCalls = []; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', }, userAuth: { async findWechatUserByOpenid() { return { userId: 'user-1', status: 'active', nickname: '毕升' }; }, async recordWechatMpMessage() { return { inserted: true }; }, async finishWechatMpMessage(input) { apiCalls.push(['finish', input.status, input.agentSessionId]); }, }, scheduleService: { async createDailyTodoDigest(input) { apiCalls.push(['digest', input.userId, input.hour, input.minute, input.channel]); return { id: 'sub-1', userId: input.userId, hour: input.hour, minute: input.minute, timezone: input.timezone, channel: input.channel, }; }, }, apiFetch: async (pathname) => { apiCalls.push(['agent', pathname]); return new Response('{}', { status: 200 }); }, }); const result = await service.handleInboundMessage( inboundXml({ content: '每天早上 7 点给我发一天的待办记录' }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }, ); assert.equal(result.status, 200); assert.match(result.body, /每天早上 7点/); assert.deepEqual(apiCalls, [ ['digest', 'user-1', 7, 0, 'wechat'], ['finish', 'done', null], ]); }); test('wechat mp service records plain todo without routing to agent', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const apiCalls = []; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', }, userAuth: { async findWechatUserByOpenid() { return { userId: 'user-1', status: 'active', nickname: '毕升' }; }, async recordWechatMpMessage() { return { inserted: true }; }, async finishWechatMpMessage(input) { apiCalls.push(['finish', input.status, input.agentSessionId]); }, }, scheduleService: { async createItem(input) { apiCalls.push(['todo', input.userId, input.kind, input.title, input.sourceChannel]); return { id: 'item-1', title: input.title, }; }, }, apiFetch: async (pathname) => { apiCalls.push(['agent', pathname]); return new Response('{}', { status: 200 }); }, }); const result = await service.handleInboundMessage( inboundXml({ content: '帮我记一下「🍽️ 跟段吃饭」' }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }, ); assert.equal(result.status, 200); assert.match(result.body, /已记录到待办列表/); assert.deepEqual(apiCalls, [ ['todo', 'user-1', 'task', '🍽️ 跟段吃饭', 'wechat'], ['finish', 'done', null], ]); }); test('wechat mp service creates balance low alert without routing to agent', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const apiCalls = []; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', }, userAuth: { async findWechatUserByOpenid() { return { userId: 'user-1', status: 'active', nickname: '毕升' }; }, async recordWechatMpMessage() { return { inserted: true }; }, async finishWechatMpMessage(input) { apiCalls.push(['finish', input.status, input.agentSessionId]); }, }, scheduleService: { async createBalanceLowAlert(input) { apiCalls.push(['balance', input.userId, input.thresholdCents, input.sourceChannel]); return { id: 'alert-1', thresholdCents: input.thresholdCents, }; }, }, apiFetch: async (pathname) => { apiCalls.push(['agent', pathname]); return new Response('{}', { status: 200 }); }, }); const result = await service.handleInboundMessage(inboundXml({ content: '余额低于 20 元提醒我' }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }); assert.equal(result.status, 200); assert.match(result.body, /已设置:当余额低于 20\.00 元时,我会通过服务号提醒你。/); assert.deepEqual(apiCalls, [ ['balance', 'user-1', 2000, 'wechat'], ['finish', 'done', null], ]); }); test('wechat mp service asks user to retry when voice recognition is empty', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; let replyCalled = false; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', }, userAuth: { async findWechatUserByOpenid() { return { userId: 'user-1', status: 'active', nickname: '毕升' }; }, async insertWechatMpMessageDetail() {}, }, apiFetch: async (pathname) => { if (pathname.includes('/reply')) replyCalled = true; return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }); }, }); const result = await service.handleInboundMessage( inboundXml({ msgType: 'voice', content: '', extraFields: { Format: 'amr' }, }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }, ); assert.equal(result.status, 200); assert.match(result.body, /未识别到语音文字/); assert.equal(replyCalled, false); }); test('wechat mp service falls back to ASR when voice recognition is empty', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; let replyCalled = false; const prompts = []; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', asrTarget: 'https://asr.example.com', }, userAuth: { async findWechatUserByOpenid() { return { userId: 'user-1', status: 'active', nickname: '毕升' }; }, async getWechatAgentRoute() { return { agentSessionId: 'session-1' }; }, async clearWechatAgentRoute() {}, async canUseChat() { return { ok: true }; }, 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() {}, async insertWechatMpMessageDetail() {}, }, 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-voice-fallback","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"我收到你的语音了。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-voice-fallback","token_state":{"inputTokens":1,"outputTokens":2}}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } if (pathname === '/sessions/session-1/reply') { replyCalled = true; const body = JSON.parse(init.body); prompts.push(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, init = {}) => { 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/media/get')) { return new Response(Buffer.from('fake-amr-audio'), { status: 200, headers: { 'Content-Type': 'audio/amr' }, }); } if (String(url).includes('https://asr.example.com/asr/oneshot')) { assert.equal(init.method, 'POST'); return new Response(JSON.stringify({ code: 200, data: { text: '帮我整理今天的会议纪要' } }), { 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-voice-fallback'; try { const result = await service.handleInboundMessage( inboundXml({ msgType: 'voice', content: '', extraFields: { MediaId: 'media-1', Format: 'amr' }, }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }, ); assert.equal(result.status, 200); assert.doesNotMatch(result.body, /未识别到语音文字/); await result.task; assert.equal(replyCalled, true); assert.match(prompts[0], /帮我整理今天的会议纪要/); } finally { crypto.randomUUID = originalRandomUuid; } }); test('wechat mp wildcard media access persists image and routes image url into agent prompt', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const testUserId = 'test-user-image'; const prompts = []; const metadataCalls = []; const detailCalls = []; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', mediaPublicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', progressDelayMs: 0, maxImageBytes: 1024 * 1024, mediaAnalysisGrayUsers: ['*'], }, userAuth: { async findWechatUserByOpenid() { return { userId: testUserId, status: 'active', nickname: '毕升' }; }, async getWechatAgentRoute() { return { agentSessionId: 'session-1' }; }, async clearWechatAgentRoute() {}, async canUseChat() { return { ok: true }; }, 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() {}, async recordWechatMpMessage() { return { inserted: true }; }, async finishWechatMpMessage() {}, async insertWechatMpMessageDetail(input) { detailCalls.push(input); }, }, 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-image","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"请告诉我希望怎么处理这张图片。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-image","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); prompts.push(body.user_message.content[0].text); metadataCalls.push(body.user_message.metadata); 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, init = {}) => { 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/media/get')) { return new Response(Buffer.from([0x89, 0x50, 0x4e, 0x47]), { status: 200, headers: { 'Content-Type': 'image/png' }, }); } 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-image'; try { const result = await service.handleInboundMessage( inboundXml({ msgType: 'image', content: '', extraFields: { MediaId: 'media-1', PicUrl: 'https://wx.example.com/image.png' }, }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }, ); assert.equal(result.status, 200); assert.match(result.body, //); await result.task; } finally { crypto.randomUUID = originalRandomUuid; fs.rmSync(path.join(process.cwd(), 'MindSpace', testUserId), { recursive: true, force: true, }); } assert.equal(prompts.length, 1); assert.match(prompts[0], /【微信服务号图片消息】/); assert.match( prompts[0], /\[图片1\]: https:\/\/example\.com\/MindSpace\/test-user-image\/public\/wechat-mp\//, ); assert.equal(metadataCalls.length, 1); assert.equal(metadataCalls[0].source, 'wechat_mp'); assert.equal(metadataCalls[0].msgType, 'image'); assert.equal(metadataCalls[0].imageUrls.length, 1); assert.match(metadataCalls[0].imageUrls[0], /\/public\/wechat-mp\//); assert.equal(detailCalls.length, 1); assert.match(detailCalls[0].mediaPublicUrl, /\/wechat-mp\//); }); test('wechat mp image submission reuses the H5 prepared reply path', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const testUserId = 'test-user-image-prepared'; const submitCalls = []; const service = createBoundWechatService({ token, config: { mediaAnalysisGrayUsers: [testUserId], }, userAuth: { async findWechatUserByOpenid() { return { userId: testUserId, status: 'active', nickname: '唐' }; }, }, sessionApiFetch: async (_sessionId, pathname) => { if (pathname === '/sessions/session-1/events') { return new Response( [ 'data: {"type":"Message","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"图片内容已识别。"}]}}\n\n', 'data: {"type":"Finish"}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } 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}`); }, submitSessionReply: async (input) => { submitCalls.push(input); return { ok: true }; }, 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/media/get')) { return new Response(Buffer.from([0x89, 0x50, 0x4e, 0x47]), { status: 200, headers: { 'Content-Type': 'image/png' }, }); } 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}`); }, }); try { const result = await service.handleInboundMessage( inboundXml({ msgType: 'image', content: '', extraFields: { MediaId: 'media-prepared', PicUrl: 'https://wx.example.com/image.png' }, }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce) }, ); await result.task; } finally { fs.rmSync(path.join(process.cwd(), 'MindSpace', testUserId), { recursive: true, force: true }); } assert.equal(submitCalls.length, 1); assert.equal(submitCalls[0].userId, testUserId); assert.equal(submitCalls[0].sessionId, 'session-1'); assert.equal(submitCalls[0].userMessage.metadata.imageUrls.length, 1); assert.match(submitCalls[0].userMessage.metadata.imageUrls[0], /\/public\/wechat-mp\//); }); test('wechat mp serializes image and follow-up text and reattaches recent image', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const testUserId = 'test-user-image-followup'; const submitCalls = []; let eventCall = 0; let releaseFirst = null; const service = createBoundWechatService({ token, config: { mediaAnalysisGrayUsers: [testUserId], }, userAuth: { async findWechatUserByOpenid() { return { userId: testUserId, status: 'active', nickname: '唐' }; }, }, sessionApiFetch: async (_sessionId, pathname) => { if (pathname === '/sessions/session-1/events') { eventCall += 1; if (eventCall === 1) { return new Response( new ReadableStream({ start(controller) { releaseFirst = () => { controller.enqueue( new TextEncoder().encode( 'data: {"type":"Message","message":{"id":"assistant-image","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"图片已识别。"}]}}\n\n' + 'data: {"type":"Finish"}\n\n', ), ); controller.close(); }; }, }), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } return new Response( [ 'data: {"type":"Message","message":{"id":"assistant-followup","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"已结合刚才图片分析主题。"}]}}\n\n', 'data: {"type":"Finish"}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } 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}`); }, submitSessionReply: async (input) => { submitCalls.push(input); return { ok: true }; }, 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/media/get')) { return new Response(Buffer.from([0x89, 0x50, 0x4e, 0x47]), { status: 200, headers: { 'Content-Type': 'image/png' }, }); } 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}`); }, }); try { const imageResult = await service.handleInboundMessage( inboundXml({ msgType: 'image', content: '', extraFields: { MediaId: 'media-followup', PicUrl: 'https://wx.example.com/image.png' }, }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce) }, ); while (submitCalls.length === 0) await new Promise((resolve) => setTimeout(resolve, 0)); const followupResult = await service.handleInboundMessage( inboundXml({ msgType: 'text', content: '请根据刚才图片分析主题' }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce) }, ); await new Promise((resolve) => setTimeout(resolve, 0)); assert.equal(submitCalls.length, 1); releaseFirst(); await imageResult.task; await followupResult.task; } finally { fs.rmSync(path.join(process.cwd(), 'MindSpace', testUserId), { recursive: true, force: true }); } assert.equal(submitCalls.length, 2); assert.deepEqual( submitCalls[1].userMessage.metadata.imageUrls, submitCalls[0].userMessage.metadata.imageUrls, ); assert.equal(submitCalls[1].userMessage.metadata.msgType, 'text'); }); test('wechat mp groups consecutive images for one follow-up and clears the consumed batch', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const testUserId = 'test-user-multi-image-followup'; const submitCalls = []; let eventCall = 0; let releaseFirst = null; const service = createBoundWechatService({ token, config: { mediaAnalysisGrayUsers: [testUserId], }, userAuth: { async findWechatUserByOpenid() { return { userId: testUserId, status: 'active', nickname: '唐' }; }, }, sessionApiFetch: async (_sessionId, pathname) => { if (pathname === '/sessions/session-1/events') { eventCall += 1; if (eventCall === 1) { return new Response( new ReadableStream({ start(controller) { releaseFirst = () => { controller.enqueue( new TextEncoder().encode( 'data: {"type":"Message","message":{"id":"assistant-first","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"第一张处理完成。"}]}}\n\n' + 'data: {"type":"Finish"}\n\n', ), ); controller.close(); }; }, }), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } return new Response( [ 'data: {"type":"Message","message":{"id":"assistant-ok","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"处理完成。"}]}}\n\n', 'data: {"type":"Finish"}\n\n', ].join(''), { status: 200, headers: { 'Content-Type': 'text/event-stream' } }, ); } 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}`); }, submitSessionReply: async (input) => { submitCalls.push(input); return { ok: true }; }, 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/media/get')) { return new Response(Buffer.from([0x89, 0x50, 0x4e, 0x47]), { status: 200, headers: { 'Content-Type': 'image/png' }, }); } 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}`); }, }); try { const firstImageResult = await service.handleInboundMessage( inboundXml({ msgType: 'image', content: '', extraFields: { MsgId: '10011', MediaId: 'media-first', PicUrl: 'https://wx.example.com/media-first.png', }, }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce) }, ); while (submitCalls.length === 0) await new Promise((resolve) => setTimeout(resolve, 0)); const secondImageResult = await service.handleInboundMessage( inboundXml({ msgType: 'image', content: '', extraFields: { MsgId: '10012', MediaId: 'media-second', PicUrl: 'https://wx.example.com/media-second.png', }, }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce) }, ); const followupResult = await service.handleInboundMessage( inboundXml({ content: '请结合刚才两张图片分析主题', extraFields: { MsgId: '10013' }, }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce) }, ); await new Promise((resolve) => setTimeout(resolve, 0)); assert.equal(submitCalls.length, 1); releaseFirst(); await firstImageResult.task; await secondImageResult.task; await followupResult.task; const laterResult = await service.handleInboundMessage( inboundXml({ content: '请再次分析刚才图片', extraFields: { MsgId: '10014' }, }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce) }, ); await laterResult.task; } finally { fs.rmSync(path.join(process.cwd(), 'MindSpace', testUserId), { recursive: true, force: true }); } assert.equal(submitCalls.length, 4); assert.equal(submitCalls[0].userMessage.metadata.imageUrls.length, 1); assert.equal(submitCalls[1].userMessage.metadata.imageUrls.length, 1); assert.equal(submitCalls[2].userMessage.metadata.imageUrls.length, 2); assert.match(submitCalls[2].userMessage.metadata.imageUrls[0], /media-first/); assert.match(submitCalls[2].userMessage.metadata.imageUrls[1], /media-second/); assert.equal(submitCalls[2].userMessage.metadata.msgType, 'text'); assert.equal(submitCalls[3].userMessage.metadata.imageUrls, undefined); }); test('wechat mp service persists Word and Excel files in user public area and reuses H5 attachment metadata', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const testUserId = 'test-user-wechat-file'; const userMessages = []; const service = createBoundWechatService({ token, config: { maxFileBytes: 1024 * 1024, acceptFile: true, mediaAnalysisGrayUsers: [testUserId], }, userAuth: { async findWechatUserByOpenid() { return { userId: testUserId, status: 'active', nickname: '毕升' }; }, }, sessionApiFetch: async (_sessionId, pathname, init = {}) => { if (pathname === '/sessions/session-1/events') { return new Response( [ 'data: {"type":"Message","request_id":"req-file","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"文件已解析。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-file","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); userMessages.push(body.user_message); 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/media/get')) { return new Response(Buffer.from('fake-docx-content'), { status: 200, headers: { 'Content-Type': 'application/octet-stream' }, }); } 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-file'; try { for (const [index, filename] of ['季度分析.docx', '销售数据.xlsx'].entries()) { const result = await service.handleInboundMessage( inboundXml({ msgType: 'file', content: '', extraFields: { MediaId: `file-media-${index + 1}`, FileName: filename, FileSize: 17, }, }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce) }, ); assert.equal(result.status, 200); await result.task; const attachment = userMessages.at(-1)?.metadata?.fileAttachments?.[0]; const publicFilename = decodeURIComponent(new URL(attachment.downloadUrl).pathname.split('/').at(-1)); assert.equal( fs.existsSync( path.join(process.cwd(), 'MindSpace', testUserId, 'public', 'wechat-mp', publicFilename), ), true, ); } } finally { crypto.randomUUID = originalRandomUuid; fs.rmSync(path.join(process.cwd(), 'MindSpace', testUserId), { recursive: true, force: true, }); } assert.equal(userMessages.length, 2); assert.match(userMessages[0].content[0].text, /【微信服务号文件消息】/); assert.match(userMessages[0].content[0].text, /\[文件1: 季度分析\.docx\]:/); assert.equal(userMessages[0].metadata.msgType, 'file'); assert.equal(userMessages[0].metadata.fileAttachments.length, 1); assert.equal(userMessages[0].metadata.fileAttachments[0].filename, '季度分析.docx'); assert.equal( userMessages[0].metadata.fileAttachments[0].mimeType, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', ); assert.match(userMessages[0].metadata.fileAttachments[0].downloadUrl, /\/public\/wechat-mp\//); assert.match(userMessages[1].content[0].text, /\[文件1: 销售数据\.xlsx\]:/); assert.equal(userMessages[1].metadata.fileAttachments[0].filename, '销售数据.xlsx'); assert.equal( userMessages[1].metadata.fileAttachments[0].mimeType, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ); assert.match(userMessages[1].metadata.fileAttachments[0].downloadUrl, /\/public\/wechat-mp\//); }); test('wechat mp service rejects unsupported public file types without entering the agent flow', async () => { let mediaDownloads = 0; let sessionCalls = 0; const service = createBoundWechatService({ config: { acceptFile: true, mediaAnalysisGrayUsers: ['user-1'] }, sessionApiFetch: async () => { sessionCalls += 1; throw new Error('session should not be called'); }, 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/media/get')) mediaDownloads += 1; throw new Error(`unexpected wechat url: ${url}`); }, }); const result = await service.handleInboundMessage( inboundXml({ msgType: 'file', content: '', extraFields: { MediaId: 'file-media-2', FileName: 'payload.exe' }, }), { timestamp: '1710000000', nonce: 'nonce', signature: signatureFor('token', '1710000000', 'nonce'), }, ); assert.equal(result.status, 200); assert.match(result.body, /仅支持 Word/); assert.equal(mediaDownloads, 0); assert.equal(sessionCalls, 0); }); test('wechat mp service keeps file analysis disabled outside the media gray allowlist', async () => { let mediaDownloads = 0; let sessionCalls = 0; const service = createBoundWechatService({ config: { acceptFile: true, mediaAnalysisGrayUsers: ['唐'], }, sessionApiFetch: async () => { sessionCalls += 1; throw new Error('session should not be called'); }, wechatFetch: async (url) => { if (String(url).includes('/cgi-bin/media/get')) mediaDownloads += 1; throw new Error(`unexpected wechat url: ${url}`); }, }); const result = await service.handleInboundMessage( inboundXml({ msgType: 'file', content: '', extraFields: { MediaId: 'file-media-gray', FileName: '测试.docx' }, }), { timestamp: '1710000000', nonce: 'nonce', signature: signatureFor('token', '1710000000', 'nonce'), }, ); assert.equal(result.status, 200); assert.match(result.body, /尚未开启服务号文件分析灰度/); assert.equal(mediaDownloads, 0); assert.equal(sessionCalls, 0); }); test('wechat mp service accepts full voice xml payload and routes recognition text into agent', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const prompts = []; const metadataCalls = []; const wechatCalls = []; const service = createBoundWechatService({ token, userAuth: { async insertWechatMpMessageDetail(input) { metadataCalls.push(input); }, }, 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-voice","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"我收到你的语音了。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-voice","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); prompts.push(body.user_message.content[0].text); metadataCalls.push(body.user_message.metadata); 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, 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 = () => 'req-voice'; try { const result = await service.handleInboundMessage( inboundXml({ msgType: 'voice', content: '', extraFields: { MediaId: 'voice-media-1', Format: 'amr', Recognition: '帮我整理今天的会议纪要', }, }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }, ); assert.equal(result.status, 200); assert.match(result.body, //); await result.task; } finally { crypto.randomUUID = originalRandomUuid; } assert.equal(prompts.length, 1); assert.match(prompts[0], /【微信服务号语音消息】/); assert.match(prompts[0], /用户语音识别文本:帮我整理今天的会议纪要/); assert.equal(metadataCalls.some((item) => item?.msgType === 'voice'), true); assert.equal( wechatCalls.some(([, , body]) => String(body ?? '').includes('我收到你的语音了')), true, ); }); test('wechat mp service accepts full location xml payload and routes structured location prompt', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const prompts = []; const detailCalls = []; const service = createBoundWechatService({ token, userAuth: { async insertWechatMpMessageDetail(input) { detailCalls.push(input); }, }, 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-location","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"你附近有几家咖啡馆。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-location","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); prompts.push(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-location'; try { const result = await service.handleInboundMessage( inboundXml({ msgType: 'location', content: '', extraFields: { Location_X: '31.2304', Location_Y: '121.4737', Scale: '15', Label: '上海市黄浦区人民广场', }, }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }, ); assert.equal(result.status, 200); assert.match(result.body, //); await result.task; } finally { crypto.randomUUID = originalRandomUuid; } assert.equal(prompts.length, 1); assert.match(prompts[0], /【微信服务号位置消息】/); assert.match(prompts[0], /地址:上海市黄浦区人民广场/); assert.match(prompts[0], /纬度:31.2304/); assert.match(prompts[0], /经度:121.4737/); assert.equal(detailCalls.length, 1); assert.equal(detailCalls[0].locationLabel, '上海市黄浦区人民广场'); }); test('wechat mp service accepts full link xml payload and routes structured link prompt', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const prompts = []; const detailCalls = []; const service = createBoundWechatService({ token, userAuth: { async insertWechatMpMessageDetail(input) { detailCalls.push(input); }, }, 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-link","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"这个链接看起来是在介绍 TKMind 的新功能。"}]}}\n\n', 'data: {"type":"Finish","request_id":"req-link","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); prompts.push(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-link'; try { const result = await service.handleInboundMessage( inboundXml({ msgType: 'link', content: '', extraFields: { Title: 'TKMind 发布说明', Description: '一篇关于多模态服务号入口的介绍', Url: 'https://example.com/articles/wechat-multimodal', }, }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }, ); assert.equal(result.status, 200); assert.match(result.body, //); await result.task; } finally { crypto.randomUUID = originalRandomUuid; } assert.equal(prompts.length, 1); assert.match(prompts[0], /【微信服务号链接消息】/); assert.match(prompts[0], /标题:TKMind 发布说明/); assert.match(prompts[0], /描述:一篇关于多模态服务号入口的介绍/); assert.match(prompts[0], /URL:https:\/\/example\.com\/articles\/wechat-multimodal/); assert.equal(detailCalls.length, 1); assert.equal(detailCalls[0].linkTitle, 'TKMind 发布说明'); }); test('wechat mp service records location events without routing to agent', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const detailCalls = []; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', }, userAuth: { async insertWechatMpMessageDetail(input) { detailCalls.push(input); }, }, apiFetch: async () => { throw new Error('should not call agent api'); }, }); const result = await service.handleInboundMessage( inboundXml({ msgType: 'event', content: '', event: 'LOCATION', extraFields: { Latitude: 31.2, Longitude: 121.5, Precision: 15 }, }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }, ); assert.equal(result.status, 200); assert.equal(result.body, 'success'); assert.equal(detailCalls.length, 1); assert.equal(detailCalls[0].msgType, 'event'); }); test('wechat mp service returns status text for question mark probes', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', statusText: '我在这边', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', }, userAuth: { async findWechatUserByOpenid() { return { userId: 'user-1', status: 'active', nickname: '毕升' }; }, }, apiFetch: async () => new Response('{}', { status: 200 }), }); const result = await service.handleInboundMessage(inboundXml({ content: '?' }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }); assert.equal(result.status, 200); assert.match(result.body, /我在这边/); assert.match(result.body, /毕升/); }); test('wechat mp service greets bound users with stored wechat nickname', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; let replyCalled = false; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', }, userAuth: { async findWechatUserByOpenid() { return { userId: 'user-1', status: 'active', nickname: '毕升' }; }, }, apiFetch: async (pathname) => { if (pathname === '/sessions/session-1/reply') replyCalled = true; return new Response('{}', { status: 200 }); }, }); const result = await service.handleInboundMessage(inboundXml({ content: '你好' }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }); assert.equal(result.status, 200); assert.match(result.body, /你好,毕升/); assert.equal(replyCalled, false); }); test('wechat mp service replies to connectivity test without agent', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; let replyCalled = false; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', }, userAuth: { async findWechatUserByOpenid() { return { userId: 'user-1', status: 'active', nickname: '毕升' }; }, }, apiFetch: async (pathname) => { if (pathname.includes('/reply')) replyCalled = true; return new Response('{}', { status: 200 }); }, }); const result = await service.handleInboundMessage(inboundXml({ content: '测试999' }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }); assert.equal(result.status, 200); assert.match(result.body, /公众号消息通道正常/); assert.equal(replyCalled, false); }); test('wechat mp service ignores duplicated message ids', async () => { const token = 'token'; const timestamp = '1710000000'; const nonce = 'nonce'; let replyCalled = false; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token, publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', }, userAuth: { async findWechatUserByOpenid() { return { userId: 'user-1', status: 'active' }; }, async recordWechatMpMessage() { return { inserted: false }; }, }, apiFetch: async (pathname) => { if (pathname === '/sessions/session-1/reply') replyCalled = true; return new Response('{}', { status: 200 }); }, }); const result = await service.handleInboundMessage(inboundXml({ content: '帮我继续处理' }), { timestamp, nonce, signature: signatureFor(token, timestamp, nonce), }); assert.equal(result.status, 200); assert.match(result.body, /上一条如果还没完成/); assert.equal(replyCalled, false); }); test('wechat mp service exposes route status and can recreate route for bound user', async () => { const calls = []; let routeCleared = false; let routeUpserted = false; const service = createWechatMpService({ config: { enabled: true, appId: 'wx123', appSecret: 'secret', token: 'token', publicBaseUrl: 'https://example.com', bindPath: '/auth/wechat/authorize?intent=login', ackText: 'ack', unsupportedText: 'unsupported', unboundTextPrefix: '请先绑定', }, userAuth: { async getWechatOpenidForUser() { return 'openid-1'; }, async getWechatAgentRoute(_appId, _openid) { if (routeCleared && !routeUpserted) return null; return { agentSessionId: routeUpserted ? 'session-new' : 'session-old', status: 'active', updatedAt: 1710000000000, }; }, async clearWechatAgentRoute() { routeCleared = true; calls.push('cleared'); }, async canUseChat() { return { ok: true }; }, 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(_userId, sessionId) { calls.push(`register:${sessionId}`); }, async upsertWechatAgentRoute() { routeUpserted = true; calls.push('upserted'); }, }, apiFetch: async (pathname) => { if (pathname === '/agent/start') { return new Response(JSON.stringify({ id: 'session-new' }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); } throw new Error(`unexpected fallback api path: ${pathname}`); }, sessionApiFetch: async (_sessionId, pathname) => { if (pathname === '/sessions/session-new') { return new Response(JSON.stringify({ working_dir: '/tmp/user-1' }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); } if (pathname === '/sessions/session-new/extensions') { return new Response(JSON.stringify({ extensions: [] }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); } if ( pathname === '/agent/restart' ) { return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' }, }); } throw new Error(`unexpected api path: ${pathname}`); }, }); const status = await service.getRouteStatusForUser('user-1'); assert.deepEqual(status, { enabled: true, bound: true, appId: 'wx123', openid: 'openid-1', agentSessionId: 'session-old', routeStatus: 'active', updatedAt: 1710000000000, }); const recreated = await service.recreateRouteForUser('user-1'); 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'), true); assert.equal(calls.includes('upserted'), true); });