Add smart ACK provider for WeChat MP replies

Replace fixed ackText with a rule-based AckProvider that picks
response templates by message type and intent (translate, summary,
rewrite, poster, ppt, mindmap, code, search, schedule). Pure sync,
zero I/O, auto-falls back to config.ackText on any error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
john
2026-06-26 15:19:03 +08:00
parent 9ed4fd48d7
commit 9b4a25799f
162 changed files with 17276 additions and 2054 deletions
+83
View File
@@ -108,6 +108,89 @@ test('getSpace scopes space and categories to the authenticated user', async ()
assert.deepEqual(calls[1].params, ['user-1', 'space-1']);
});
test('getSpace includes schedule snapshot when schedule service is available', async () => {
const pool = {
async query(sql, params) {
if (sql.includes('FROM h5_user_spaces')) {
return [[{
id: 'space-1',
user_id: 'user-1',
space_name: '我的空间',
quota_bytes: 5 * 1024 * 1024,
used_bytes: 1024,
reserved_bytes: 2048,
status: 'active',
created_at: 10,
updated_at: 20,
}]];
}
if (sql.includes('FROM h5_space_categories')) {
return [[{
id: 'category-1',
category_code: 'private',
category_name: '私人区',
visibility_policy: 'private',
ai_access_policy: 'explicit_asset_grant',
publish_policy: 'desensitized_copy_only',
is_system: 1,
sort_order: 20,
item_count: 0,
}]];
}
if (sql.includes('FROM h5_publish_records')) {
return [[{ public_page_used: 0, monthly_view_used: 0 }]];
}
return [[]];
},
};
const scheduleService = {
async listTodayTodoItems({ userId }) {
assert.equal(userId, 'user-1');
return [
{
id: 'item-1',
kind: 'task',
title: '跟进报价单',
description: '来自服务号',
status: 'active',
startAt: null,
endAt: null,
dueAt: Date.UTC(2026, 5, 18, 1, 30, 0),
allDay: false,
timezone: 'Asia/Shanghai',
location: null,
createdAt: 10,
updatedAt: 20,
},
];
},
async listDigestSubscriptions({ userId, digestType, status }) {
assert.equal(userId, 'user-1');
assert.equal(digestType, 'todo_day');
assert.deepEqual(status, ['active', 'locked']);
return [
{
id: 'digest-1',
hour: 7,
minute: 0,
timezone: 'Asia/Shanghai',
channel: 'wechat',
status: 'active',
nextRunAt: Date.UTC(2026, 5, 18, 23, 0, 0),
lastRunAt: null,
attempts: 0,
},
];
},
};
const service = createMindSpaceService(pool, { scheduleService });
const space = await service.getSpace('user-1');
assert.equal(space.schedule.todayTodoItems[0].title, '跟进报价单');
assert.equal(space.schedule.digestSubscriptions[0].hour, 7);
});
test('getSpace returns null when the user has no space', async () => {
const service = createMindSpaceService({
async query() {