feat: sync portal runtime fixes for release

This commit is contained in:
john
2026-06-27 22:28:41 +08:00
parent 8264e71f9e
commit 4a9bc710f1
14 changed files with 795 additions and 130 deletions
+440
View File
@@ -7,6 +7,7 @@ import {
createWechatMpService,
guardMissingPublicHtmlLinks,
loadWechatMpConfig,
maybeAttachPublishedHtmlLink,
splitWechatText,
verifyWechatMpSignature,
verifyWechatMpUrlChallenge,
@@ -136,6 +137,48 @@ test('guardMissingPublicHtmlLinks blocks missing MindSpace public html links', a
assert.match(guarded, /missing\.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, '<!doctype html><title>Hello</title>');
const text = await maybeAttachPublishedHtmlLink(
{
text: '已完成',
messages: [
{
role: 'assistant',
content: [
{
type: 'toolRequest',
toolCall: {
value: {
name: 'developer',
arguments: {
action: 'write',
path: htmlPath,
content: '<!doctype html><title>Hello</title>',
},
},
},
},
],
},
],
},
{
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('wechat mp service splits long agent replies into multiple customer messages', async () => {
const token = 'token';
const timestamp = '1710000000';
@@ -367,6 +410,70 @@ test('wechat mp service strips markdown emphasis around outbound links', async (
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}`);
},
});
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',
@@ -873,6 +980,339 @@ test('wechat mp service injects schedule skill instructions for reminder-like re
}
});
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, '<!doctype html><title>Hello</title>');
}
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-tool","role":"assistant","metadata":{"userVisible":true},"content":[{"type":"toolRequest","toolCall":{"value":{"name":"developer","arguments":{"action":"write","path":"${htmlPath.replace(/\\/g, '\\\\')}","content":"<!doctype html><title>Hello</title>"}}}}]}}\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}`);
},
});
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 blocks schedule confirmation when no schedule item was written', async () => {
const token = 'token';
const timestamp = '1710000000';