feat(wechat): channel split with stub-safe page delivery

Introduce wechat/ channel package; never send stub HTML links to service-account users.
This commit was merged in pull request #2.
This commit is contained in:
2026-07-04 05:21:13 +00:00
parent f36e8b9292
commit 2a3579c73a
8 changed files with 413 additions and 24 deletions
+76
View File
@@ -0,0 +1,76 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { classifyWechatIntent, isPageGenerateIntent } from './intent/classifier.mjs';
import {
filterSendableHtmlArtifacts,
isStubPublicHtmlContent,
selectSendableHtmlArtifacts,
verifyPageArtifactContent,
} from './verify/page-artifact.mjs';
import { resolvePageGenerateOutcome } from './handlers/page-generate.mjs';
test('classifyWechatIntent detects page.generate', () => {
const intent = classifyWechatIntent({
msgType: 'text',
agentText: '帮我生成一个唐诗页面,放一首诗',
});
assert.equal(intent.kind, 'page.generate');
assert.match(intent.topic, /唐诗页面/);
assert.equal(isPageGenerateIntent({ msgType: 'text', agentText: intent.topic }), true);
});
test('classifyWechatIntent detects session.reset', () => {
assert.equal(classifyWechatIntent({ msgType: 'text', agentText: '换话题' }).kind, 'session.reset');
});
test('selectSendableHtmlArtifacts never returns stub html', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'wechat-stub-'));
const stubPath = path.join(dir, 'tang-poem.html');
fs.writeFileSync(
stubPath,
'<html>服务号自动补出简版页面</html>',
'utf8',
);
const realPath = path.join(dir, 'real.html');
fs.writeFileSync(
realPath,
`<!doctype html><html><body><main>${'x'.repeat(600)}</main></body></html>`,
'utf8',
);
const artifacts = [
{ localPath: stubPath, relativePath: 'public/tang-poem.html', url: 'https://example/tang-poem.html' },
{ localPath: realPath, relativePath: 'public/real.html', url: 'https://example/real.html' },
];
const sendable = selectSendableHtmlArtifacts({ confirmedArtifacts: artifacts });
assert.equal(sendable.length, 1);
assert.match(sendable[0].relativePath, /real\.html/);
assert.equal(isStubPublicHtmlContent('<html>服务号自动补出简版页面</html>'), true);
});
test('resolvePageGenerateOutcome fails when only stub artifacts exist', () => {
const outcome = resolvePageGenerateOutcome({
reply: { text: '页面已生成 https://m.tkmind.cn/MindSpace/u/public/tang-poem.html' },
confirmedArtifacts: [
{
localPath: '/tmp/stub.html',
relativePath: 'public/tang-poem.html',
},
],
htmlGenerationNeedsRetry: true,
replyHasPublicLinks: true,
});
assert.equal(outcome.action, 'fail');
assert.match(outcome.failureText, /服务号页面技能/);
});
test('verifyPageArtifactContent rejects small stub files', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'wechat-verify-'));
const file = path.join(dir, 'page.html');
fs.writeFileSync(file, '<html>服务号兜底</html>', 'utf8');
const artifact = { localPath: file, relativePath: 'public/page.html' };
assert.equal(verifyPageArtifactContent(artifact).ok, false);
assert.equal(filterSendableHtmlArtifacts([artifact]).length, 0);
});