fix: guard WeChat Page Data delivery with Aider review
Memind CI / Test, build, and release guards (push) Has been cancelled

This commit is contained in:
john
2026-07-28 00:26:24 +08:00
parent bc20e93893
commit 8bfb7959fc
13 changed files with 996 additions and 7 deletions
@@ -9,6 +9,7 @@ import path from 'node:path';
import test from 'node:test';
import { createWechatMpService } from './wechat-mp.mjs';
import { buildWechatAgentPrompt } from './wechat/prompts/chat-general.mjs';
import { buildPageGenerateAgentPrompt } from './wechat/prompts/page-generate.mjs';
import {
evaluatePageDataFinishGuard,
maybeAutoBindPageDataHtmlPages,
@@ -151,6 +152,15 @@ test('buildWechatAgentPrompt injects page-data-collect requirements for survey r
assert.match(prompt, /page-data-collect/);
assert.match(prompt, /禁止 localStorage/);
assert.match(prompt, /private_data_bind_workspace_page/);
const pagePrompt = buildPageGenerateAgentPrompt({
msgType: 'text',
agentText: '帮我创建一个可以提交的调查问卷页面,后台查看记录',
});
assert.match(pagePrompt, /Page Data 强制要求/);
assert.match(pagePrompt, /private_data_execute/);
assert.match(pagePrompt, /private_data_register_dataset/);
assert.match(pagePrompt, /private_data_bind_workspace_page/);
});
test('integration: wechat mp blocks localStorage survey delivery with page-data failure notice', async () => {
@@ -247,6 +257,157 @@ test('integration: wechat mp blocks localStorage survey delivery with page-data
}
});
test('integration: WeChat Page Data runs Aider review before the delivery contract', async () => {
const token = 'token';
const timestamp = '1710000000';
const nonce = 'nonce';
const workspaceRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'wechat-page-data-review-'));
const htmlPath = path.join(workspaceRoot, 'public', 'children-diet-survey.html');
fs.mkdirSync(path.dirname(htmlPath), { recursive: true });
fs.writeFileSync(htmlPath, VALID_SURVEY_HTML, 'utf8');
const order = [];
const reviewCalls = [];
const wechatCalls = [];
let failReview = 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,
requireFreshPageThumbnail: false,
pageDataAiderReviewEnabled: true,
pageDataAiderReviewUsers: ['唐'],
},
userAuth: createWechatUserAuth(workspaceRoot),
htmlDeliveryAuthority: {
async prepareWechatHtmlDelivery() {
const artifact = {
relativePath: 'public/children-diet-survey.html',
localPath: htmlPath,
url: 'https://m.tkmind.cn/MindSpace/user-page-data/public/children-diet-survey.html',
};
return {
publishedArtifacts: [artifact],
expectedArtifacts: [artifact],
recentArtifacts: [],
confirmedArtifacts: [artifact],
verifiedArtifacts: [artifact],
validReplyUrls: [artifact.url],
hasValidReplyLink: true,
};
},
async ensureWechatFreshPageThumbnails() {
return { ok: true };
},
},
pageDataDeliveryReviewer: {
async reviewIfNeeded(input) {
order.push('aider-review');
reviewCalls.push(input);
if (failReview) {
const error = new Error('review failed');
error.code = 'PAGE_DATA_REVIEW_NOT_PASSED';
throw error;
}
return {
action: 'passed',
executor: 'aider',
reviewedFiles: input.relativePaths,
};
},
},
pageDataFinishGuard: {
async prepareWechatPageDataDelivery({ reply }) {
order.push('delivery-contract');
return {
outcome: { action: 'send' },
autoBind: { errors: [] },
deliveryArtifacts: [{
relativePath: 'public/children-diet-survey.html',
url: 'https://m.tkmind.cn/MindSpace/user-page-data/public/children-diet-survey.html',
}],
deliveryCheck: { ok: true, failures: [] },
rewrittenText: reply?.text ?? '',
};
},
},
sessionApiFetch: async (sessionId, pathname) => {
if (pathname === `/sessions/${sessionId}/events`) {
return new Response(
[
`data: {"type":"Message","request_id":"req-page-data-review","message":{"id":"assistant-tool","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"toolRequest","toolCall":{"value":{"name":"sandbox-fs__write_file","arguments":{"path":"public/children-diet-survey.html","content":"${jsonEscapeHtml(VALID_SURVEY_HTML)}"}}}}]}}\n\n`,
'data: {"type":"Message","request_id":"req-page-data-review","message":{"id":"assistant-1","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"问卷已发布:https://m.tkmind.cn/MindSpace/user-page-data/public/children-diet-survey.html"}]}}\n\n',
'data: {"type":"Finish","request_id":"req-page-data-review","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: createWechatFetchRecorder(wechatCalls),
});
const originalRandomUuid = crypto.randomUUID;
crypto.randomUUID = () => 'req-page-data-review';
try {
const result = await service.handleInboundMessage(inboundXml(), {
timestamp,
nonce,
signature: signatureFor(token, timestamp, nonce),
});
assert.equal(result.status, 200);
await result.task;
assert.deepEqual(order, ['aider-review', 'delivery-contract']);
assert.equal(reviewCalls.length, 1);
assert.equal(reviewCalls[0].sourceChannel, 'wechat_mp');
assert.equal(reviewCalls[0].sourceMessageId, '10001');
assert.equal(reviewCalls[0].forcePageData, true);
assert.deepEqual(
reviewCalls[0].relativePaths,
['public/children-diet-survey.html'],
);
failReview = true;
order.length = 0;
const outboundBeforeFailure = wechatCalls.length;
const failedResult = await service.handleInboundMessage(inboundXml(), {
timestamp,
nonce,
signature: signatureFor(token, timestamp, nonce),
});
await failedResult.task;
assert.deepEqual(order, ['aider-review']);
const failedSend = wechatCalls
.slice(outboundBeforeFailure)
.find(([url]) => String(url).includes('/cgi-bin/message/custom/send'));
assert.ok(failedSend);
const failedPayload = JSON.parse(failedSend[2]);
assert.match(failedPayload.text.content, /Page Data|数据收集|绑定/);
assert.doesNotMatch(
failedPayload.text.content,
/https:\/\/m\.tkmind\.cn\/MindSpace\/user-page-data\/public\/children-diet-survey\.html/,
);
} finally {
crypto.randomUUID = originalRandomUuid;
fs.rmSync(workspaceRoot, { recursive: true, force: true });
}
});
test('integration: finish guard auto-bind clears unbound state for valid survey html', async () => {
const workspaceRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'page-data-autobind-'));
try {