release: prepare 0629001 portal updates

This commit is contained in:
john
2026-06-29 22:20:04 +08:00
parent 18ea4f82fd
commit a40e340a41
84 changed files with 17516 additions and 2475 deletions
+190 -25
View File
@@ -9,6 +9,7 @@ import {
guardMissingPublicHtmlLinks,
loadWechatMpConfig,
maybeAttachPublishedHtmlLink,
shouldRetryHtmlGenerationReply,
splitWechatText,
verifyWechatMpSignature,
verifyWechatMpUrlChallenge,
@@ -138,7 +139,74 @@ test('guardMissingPublicHtmlLinks blocks missing MindSpace public html links', a
assert.match(guarded, /missing\.html/);
});
test('wechat mp service replaces blocked publish claims with a directly accessible fallback page', async () => {
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: '<html></html>' },
},
},
},
],
},
],
};
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('wechat mp service rejects blocked publish claims that did not use the H5 page skill', async () => {
const wechatCalls = [];
const service = createBoundWechatService({
config: {
@@ -208,8 +276,8 @@ test('wechat mp service replaces blocked publish claims with a directly accessib
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.match(payload.text.content, /summer-breeze-journal\.html/);
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) => {
@@ -2033,7 +2101,115 @@ test('wechat mp service attaches the newly generated page link even when no html
assert.doesNotMatch(payload.text.content, /页面生成未完成|旧指令污染|稍后重试/);
});
test('wechat mp service creates a simple fallback page when html generation reply produced no file', async () => {
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, '<!doctype html><title>Summer</title>');
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 refuses to fabricate a fallback page when html generation produced no file', async () => {
const token = 'token';
const timestamp = '1710000000';
const nonce = 'nonce';
@@ -2131,17 +2307,14 @@ test('wechat mp service creates a simple fallback page when html generation repl
}
const htmlPath = path.join(workspaceRoot, 'public', 'thailand-guide.html');
assert.equal(fs.existsSync(htmlPath), true);
const html = fs.readFileSync(htmlPath, 'utf8');
assert.match(html, /泰国简易攻略/);
assert.match(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, /https:\/\/m\.tkmind\.cn\/MindSpace\/.+\/public\/thailand-guide\.html/);
assert.doesNotMatch(payload.text.content, /页面生成未完成|稍后重试/);
assert.match(payload.text.content, /没有按 H5 里的页面技能真正生成成功/);
assert.doesNotMatch(payload.text.content, /https:\/\/m\.tkmind\.cn\/MindSpace\/.+\/public\/thailand-guide\.html/);
});
test('wechat mp service can deliver a fallback page without rebuilding the session on publish-success claims', async () => {
test('wechat mp service retries poisoned publish claims and still refuses fallback output', async () => {
const token = 'token';
const timestamp = '1710000000';
const nonce = 'nonce';
@@ -2149,8 +2322,6 @@ test('wechat mp service can deliver a fallback page without rebuilding the sessi
let routeCleared = false;
let started = false;
const workspaceRoot = fs.mkdtempSync('/tmp/wechat-mp-publish-claim-');
const htmlPath = path.join(workspaceRoot, 'public', 'summer-breeze-journal.html');
const service = createWechatMpService({
config: {
enabled: true,
@@ -2242,10 +2413,6 @@ test('wechat mp service can deliver a fallback page without rebuilding the sessi
});
}
if (pathname === `/sessions/${sessionId}/reply`) {
if (sessionId === 'session-2') {
fs.mkdirSync(path.dirname(htmlPath), { recursive: true });
fs.writeFileSync(htmlPath, '<!doctype html><title>Summer</title>');
}
return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
}
if (pathname === `/sessions/${sessionId}/events`) {
@@ -2260,10 +2427,8 @@ test('wechat mp service can deliver a fallback page without rebuilding the sessi
}
return new Response(
[
'data: {"type":"Message","request_id":"req-claim-retry","message":{"id":"assistant-skill","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"toolRequest","toolCall":{"value":{"name":"load_skill","arguments":{"name":"static-page-publish"}}}}]}}\n\n',
`data: {"type":"Message","request_id":"req-claim-retry","message":{"id":"assistant-tool","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"toolRequest","toolCall":{"value":{"name":"developer","arguments":{"action":"write","path":"${htmlPath.replace(/\\/g, '\\\\')}","content":"<!doctype html><title>Summer</title>"}}}}]}}\n\n`,
'data: {"type":"Message","request_id":"req-claim-retry","message":{"id":"assistant-2","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"text","text":"已完成"}]}}\n\n',
'data: {"type":"Finish","request_id":"req-claim-retry","token_state":{"inputTokens":2,"outputTokens":2}}\n\n',
'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' } },
);
@@ -2308,13 +2473,13 @@ test('wechat mp service can deliver a fallback page without rebuilding the sessi
crypto.randomUUID = originalRandomUuid;
}
assert.equal(routeCleared, false);
assert.equal(started, false);
assert.equal(routeCleared, true);
assert.equal(started, true);
const sendCall = wechatCalls.find(([url]) => String(url).includes('/cgi-bin/message/custom/send'));
const payload = JSON.parse(sendCall[2]);
assert.doesNotMatch(payload.text.content, /页面都已经成功发布了|主题页面已发布/);
assert.match(payload.text.content, /summer-breeze-journal\.html/);
assert.match(payload.text.content, /可直接打开的简版页面|查看页面/);
assert.match(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 () => {