1d165bc6e3
Expand Goose v1.49 smoke coverage (memory chat, portal resume, page e2e, multiturn provider), add canary memory policy lock, refresh baselines, and ignore one-off evidence artifacts. Document headroom-based context runtime fusion plan; include auth, scheduled-task, and wechat intent fixes on branch. Co-authored-by: Cursor <cursoragent@cursor.com>
273 lines
9.2 KiB
JavaScript
273 lines
9.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
import {
|
|
buildScheduledTaskExecutionPrompt,
|
|
buildScheduledTaskVerifiedHtmlUrls,
|
|
buildSimpleScheduledTaskReminderDelivery,
|
|
deliveryTextPromisesPublicHtml,
|
|
extractPublicHtmlPathsFromText,
|
|
extractScheduledTaskDeliveryText,
|
|
finalizeScheduledTaskPageDelivery,
|
|
formatScheduledTaskDeliveryMessage,
|
|
isSimpleScheduledTaskReminder,
|
|
looksLikeScheduledTaskNonDelivery,
|
|
reconcileStuckStaticPageDeliveryContracts,
|
|
resolveScheduledTaskDeliveryPollIntervalMs,
|
|
resolveScheduledTaskDeliveryRetryDelaysMs,
|
|
} from './scheduled-task-executor.mjs';
|
|
|
|
test('buildScheduledTaskExecutionPrompt includes task spec and automation marker', () => {
|
|
const message = buildScheduledTaskExecutionPrompt({
|
|
id: 'task-1',
|
|
title: '每日新闻页',
|
|
taskSpec: '搜索今日新闻并生成 HTML 页面',
|
|
recurrence: 'daily',
|
|
timezone: 'Asia/Shanghai',
|
|
});
|
|
const text = message.content[0].text;
|
|
assert.match(text, /scheduled-task-automation/);
|
|
assert.match(text, /每日新闻页/);
|
|
assert.match(text, /搜索今日新闻并生成 HTML 页面/);
|
|
assert.match(text, /Scheduled Automation/);
|
|
assert.match(text, /禁止调用 scheduled_task_create/);
|
|
});
|
|
|
|
test('isSimpleScheduledTaskReminder matches text-only reminder taskSpec', () => {
|
|
assert.equal(
|
|
isSimpleScheduledTaskReminder({
|
|
taskSpec: '提醒 icyxu:该吃药了,请按时服药,注意身体健康。',
|
|
}),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
isSimpleScheduledTaskReminder({
|
|
taskSpec: '搜索今日新闻并生成 HTML 页面',
|
|
}),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test('buildSimpleScheduledTaskReminderDelivery formats reminder body', () => {
|
|
assert.equal(
|
|
buildSimpleScheduledTaskReminderDelivery({
|
|
taskSpec: '提醒 icyxu:该吃药了,请按时服药,注意身体健康。',
|
|
}),
|
|
'icyxu,该吃药了,请按时服药,注意身体健康。',
|
|
);
|
|
});
|
|
|
|
test('looksLikeScheduledTaskNonDelivery detects task setup replies', () => {
|
|
assert.equal(
|
|
looksLikeScheduledTaskNonDelivery('定时自动任务已成功设置 ✅\n| 任务标题 | 每天上午10点提醒吃药 |'),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
looksLikeScheduledTaskNonDelivery('任务已经存在且处于 active 状态,无需重复创建。'),
|
|
true,
|
|
);
|
|
});
|
|
|
|
test('looksLikeScheduledTaskNonDelivery detects clarification replies', () => {
|
|
assert.equal(
|
|
looksLikeScheduledTaskNonDelivery('请问 7月31日具体几点执行这个任务?'),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
looksLikeScheduledTaskNonDelivery('页面已生成:https://example.com/news.html'),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
looksLikeScheduledTaskNonDelivery(
|
|
'页面已生成:https://m.tkmind.cn/MindSpace/user-1/public/daily-news.html',
|
|
),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
looksLikeScheduledTaskNonDelivery('好的', { readyPaths: ['public/news.html'] }),
|
|
false,
|
|
);
|
|
assert.equal(looksLikeScheduledTaskNonDelivery('验证成功'), false);
|
|
assert.equal(looksLikeScheduledTaskNonDelivery('已完成'), false);
|
|
assert.equal(looksLikeScheduledTaskNonDelivery('好的'), true);
|
|
});
|
|
|
|
test('extractPublicHtmlPathsFromText parses MindSpace public links', () => {
|
|
assert.deepEqual(
|
|
extractPublicHtmlPathsFromText(
|
|
'链接:https://m.tkmind.cn/MindSpace/u/public/daily-news-0816.html',
|
|
),
|
|
['public/daily-news-0816.html'],
|
|
);
|
|
});
|
|
|
|
test('deliveryTextPromisesPublicHtml detects page delivery replies', () => {
|
|
assert.equal(
|
|
deliveryTextPromisesPublicHtml('今日摘要:天气不错'),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
deliveryTextPromisesPublicHtml('public/daily-news.html 已生成'),
|
|
true,
|
|
);
|
|
});
|
|
|
|
test('resolveScheduledTaskDeliveryPollIntervalMs reads env override', () => {
|
|
assert.equal(
|
|
resolveScheduledTaskDeliveryPollIntervalMs({
|
|
H5_SCHEDULED_TASK_DELIVERY_POLL_INTERVAL_MS: '7500',
|
|
}),
|
|
7500,
|
|
);
|
|
});
|
|
|
|
test('resolveScheduledTaskDeliveryRetryDelaysMs keeps backward-compatible shape', () => {
|
|
assert.deepEqual(
|
|
resolveScheduledTaskDeliveryRetryDelaysMs({
|
|
H5_SCHEDULED_TASK_DELIVERY_POLL_INTERVAL_MS: '100',
|
|
}),
|
|
[0, 100, 100, 100],
|
|
);
|
|
});
|
|
|
|
test('buildScheduledTaskVerifiedHtmlUrls maps ready paths to public urls', () => {
|
|
assert.deepEqual(
|
|
buildScheduledTaskVerifiedHtmlUrls('user-1', ['public/news.html'], {
|
|
publicBaseUrl: 'https://m.tkmind.cn',
|
|
}),
|
|
['https://m.tkmind.cn/MindSpace/user-1/public/news.html'],
|
|
);
|
|
});
|
|
|
|
test('finalizeScheduledTaskPageDelivery prepares and releases static contracts', async () => {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'scheduled-task-finalize-'));
|
|
const publishDir = path.join(dir, 'MindSpace', 'user-1');
|
|
const relativePath = 'public/news.html';
|
|
await fs.mkdir(path.dirname(path.join(publishDir, relativePath)), { recursive: true });
|
|
await fs.writeFile(path.join(publishDir, relativePath), '<html></html>', 'utf8');
|
|
const calls = [];
|
|
const pool = {
|
|
async query(sql, params) {
|
|
calls.push({ sql, params });
|
|
if (sql.includes('FROM h5_page_delivery_contracts')) {
|
|
return [[{ workspace_relative_path: relativePath }]];
|
|
}
|
|
if (sql.includes('SELECT id, data_mode, status')) {
|
|
return [[{ id: 'c1', data_mode: 'static', status: 'preparing' }]];
|
|
}
|
|
if (sql.includes("SET status = 'ready'")) {
|
|
return [{ affectedRows: 1 }];
|
|
}
|
|
return [[]];
|
|
},
|
|
};
|
|
const readyPaths = await finalizeScheduledTaskPageDelivery({
|
|
pool,
|
|
userId: 'user-1',
|
|
sessionId: 'session-1',
|
|
messages: [],
|
|
publishDir,
|
|
deliveryText: 'public/news.html 已生成',
|
|
logger: { warn() {}, info() {} },
|
|
});
|
|
assert.deepEqual(readyPaths, [relativePath]);
|
|
assert.ok(calls.some((call) => call.sql.includes('INSERT INTO h5_page_delivery_contracts')));
|
|
});
|
|
|
|
test('finalizeScheduledTaskPageDelivery skips release when html file is missing', async () => {
|
|
const publishDir = path.join(await fs.mkdtemp(path.join(os.tmpdir(), 'scheduled-task-finalize-missing-')), 'MindSpace', 'user-1');
|
|
await fs.mkdir(publishDir, { recursive: true });
|
|
const pool = {
|
|
async query(sql) {
|
|
if (sql.includes('FROM h5_page_delivery_contracts')) {
|
|
return [[{ workspace_relative_path: 'public/news.html' }]];
|
|
}
|
|
if (sql.includes('SELECT id, data_mode, status')) {
|
|
return [[{ id: 'c1', data_mode: 'static', status: 'preparing' }]];
|
|
}
|
|
if (sql.includes("SET status = 'ready'")) {
|
|
throw new Error('should not mark ready without html file');
|
|
}
|
|
return [[]];
|
|
},
|
|
};
|
|
const readyPaths = await finalizeScheduledTaskPageDelivery({
|
|
pool,
|
|
userId: 'user-1',
|
|
sessionId: 'session-1',
|
|
messages: [],
|
|
publishDir,
|
|
deliveryText: 'public/news.html 已生成',
|
|
logger: { warn() {}, info() {} },
|
|
});
|
|
assert.deepEqual(readyPaths, []);
|
|
});
|
|
|
|
test('reconcileStuckStaticPageDeliveryContracts releases materialized static pages', async () => {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'scheduled-task-'));
|
|
const userId = 'user-1';
|
|
const relativePath = 'public/news.html';
|
|
const filePath = path.join(dir, 'MindSpace', userId, relativePath);
|
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
await fs.writeFile(filePath, '<html></html>', 'utf8');
|
|
const pool = {
|
|
async query(sql) {
|
|
if (sql.includes('FROM h5_page_delivery_contracts')) {
|
|
return [[{ user_id: userId, workspace_relative_path: relativePath }]];
|
|
}
|
|
if (sql.includes("SET status = 'ready'")) {
|
|
return [{ affectedRows: 1 }];
|
|
}
|
|
return [[]];
|
|
},
|
|
};
|
|
const released = await reconcileStuckStaticPageDeliveryContracts({
|
|
pool,
|
|
h5Root: dir,
|
|
logger: { info() {} },
|
|
});
|
|
assert.deepEqual(released, [{ userId, relativePath }]);
|
|
});
|
|
|
|
test('reconcileStuckStaticPageDeliveryContracts fails orphan contracts without html', async () => {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'scheduled-task-orphan-'));
|
|
const userId = 'user-1';
|
|
const relativePath = 'public/missing.html';
|
|
const pool = {
|
|
async query(sql) {
|
|
if (sql.includes('FROM h5_page_delivery_contracts')) {
|
|
return [[{ user_id: userId, workspace_relative_path: relativePath }]];
|
|
}
|
|
if (sql.includes("SET status = 'failed'")) {
|
|
return [{ affectedRows: 1 }];
|
|
}
|
|
return [[]];
|
|
},
|
|
};
|
|
const released = await reconcileStuckStaticPageDeliveryContracts({
|
|
pool,
|
|
h5Root: dir,
|
|
logger: { info() {} },
|
|
});
|
|
assert.deepEqual(released, []);
|
|
});
|
|
|
|
test('extractScheduledTaskDeliveryText reads last assistant message', () => {
|
|
const text = extractScheduledTaskDeliveryText([
|
|
{ role: 'user', content: [{ type: 'text', text: 'hi' }] },
|
|
{ role: 'assistant', content: [{ type: 'text', text: '今日新闻页:https://example.com/news.html' }] },
|
|
], { title: '每日新闻页' });
|
|
assert.match(text, /https:\/\/example.com\/news.html/);
|
|
});
|
|
|
|
test('formatScheduledTaskDeliveryMessage wraps delivery body', () => {
|
|
const text = formatScheduledTaskDeliveryMessage(
|
|
{ title: '每日新闻页' },
|
|
'页面已生成:https://example.com/news.html',
|
|
);
|
|
assert.match(text, /定时任务完成:每日新闻页/);
|
|
assert.match(text, /页面已生成/);
|
|
});
|