From e1ad094f93076456d75d6fd93ad06bed1c94e0de Mon Sep 17 00:00:00 2001 From: john Date: Tue, 28 Jul 2026 14:38:53 +0800 Subject: [PATCH] fix: retain unlinked WeChat route for immediate-context page requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Short follow-ups like "把刚才的诗做成页面" must keep the existing agent session so Goose still sees the adjacent poem in snapshot history. Rotating orphan routes before these requests dropped the poem and revived stale memory. Co-authored-by: Cursor --- wechat-mp.mjs | 8 ++++-- wechat-mp.test.mjs | 67 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/wechat-mp.mjs b/wechat-mp.mjs index 74f05a4..529b409 100644 --- a/wechat-mp.mjs +++ b/wechat-mp.mjs @@ -1984,6 +1984,7 @@ export function createWechatMpService({ userId, openid, forceNew = false, + retainUnlinkedRoute = false, userContext = null, }) => { if (forceNew) { @@ -2045,8 +2046,10 @@ export function createWechatMpService({ snapshotMessageCount: routeSnapshotMessageCount, now, }); - if (routeIsIdle || routeIsTooLong || routeHasUnlinkedConversation) { - if (routeHasUnlinkedConversation) { + const shouldRotateUnlinkedRoute = + routeHasUnlinkedConversation && !retainUnlinkedRoute; + if (routeIsIdle || routeIsTooLong || shouldRotateUnlinkedRoute) { + if (shouldRotateUnlinkedRoute) { logger.warn?.('WeChat MP orphaned route rotated before reuse:', { agentSessionId: existingRoute.agentSessionId, snapshotMessageCount: routeSnapshotMessageCount, @@ -2439,6 +2442,7 @@ export function createWechatMpService({ openid: inbound.fromUserName, userContext: user, forceNew, + retainUnlinkedRoute: immediateContextFollowup, }); let sessionId = route.sessionId; await ensureSessionProvider(sessionId); diff --git a/wechat-mp.test.mjs b/wechat-mp.test.mjs index 61511b3..c6278df 100644 --- a/wechat-mp.test.mjs +++ b/wechat-mp.test.mjs @@ -358,6 +358,73 @@ test('WeChat immediate-context page guard does not match complete page requests' assert.doesNotMatch(ordinaryPrompt, /即时上下文优先/); }); +test('WeChat immediate-context page retains an unlinked route with adjacent snapshot', async () => { + const clearedRoutes = []; + const startedSessions = []; + let activeRoute = { + agentSessionId: 'session-poem', + updatedAt: Date.now() - 120_000, + }; + const service = createBoundWechatService({ + config: { + sessionMessageRotateCount: 0, + sessionIdleRotateMs: 0, + }, + userAuth: { + async getWechatAgentRoute() { + return activeRoute; + }, + async clearWechatAgentRoute(_appId, openid) { + clearedRoutes.push(openid); + activeRoute = null; + }, + async countWechatAgentSessionMessages() { + return 0; + }, + async getWechatAgentSessionSnapshotMessageCount() { + return 5; + }, + async touchWechatAgentRoute(_appId, _openid, updatedAt) { + activeRoute = { ...activeRoute, updatedAt }; + }, + }, + startAgentSession: async (sessionId) => { + startedSessions.push(sessionId); + return sessionId; + }, + sessionApiFetch: async (pathname) => { + if (String(pathname).includes('/tools')) { + return new Response(JSON.stringify({ tools: [] }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + } + return new Response(JSON.stringify({ conversation: [] }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + }, + submitSessionReply: async () => ({ + ok: true, + conversation: [{ role: 'assistant', content: [{ type: 'text', text: 'ok' }] }], + }), + }); + + const timestamp = '1710000000'; + const nonce = 'nonce-immediate-context-route'; + await service.handleInboundMessage( + inboundXml({ content: '把刚才的诗做成页面' }), + { + timestamp, + nonce, + signature: signatureFor('token', timestamp, nonce), + }, + ); + + assert.equal(clearedRoutes.length, 0); + assert.equal(startedSessions.length, 0); +}); + test('WeChat only rotates an aged route with conversation but no linked inbound message', () => { const now = 200_000; assert.equal(