import assert from 'node:assert/strict'; import crypto from 'node:crypto'; import fs from 'node:fs'; import test from 'node:test'; import { buildWechatTextReply, createWechatMpService, guardMissingPublicHtmlLinks, loadWechatMpConfig, 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 createBoundWechatService({ token = 'token', wechatFetch, sessionApiFetch, userAuth = {}, config = {}, scheduleService = null, applySessionLlmProvider = 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, }, sessionApiFetch, 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('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('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}`); }, }); 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('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.equal(body.user_message.content[0].text, '【微信服务号新消息】请只回答下面这条用户消息,不要主动延续无关的历史话题。\n若用户只是在测试连通性,请一句话确认收到即可,不要展开旧任务。\n\n用户消息:帮我做个页面'); 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, true); 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}`); }, }); 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}`); }, }); 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 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 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 service 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 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, }, 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); 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(`/Users/john/PycharmProjects/test/test-memind/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(detailCalls.length, 1); assert.match(detailCalls[0].mediaPublicUrl, /\/wechat-mp\//); }); 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'), false); assert.equal(calls.includes('upserted'), true); });