fix(wechat): pass inbound messages to goose and gate side effects at MCP tools
Memind CI / Test, build, and release guards (push) Failing after 14m19s
Memind CI / Test, build, and release guards (push) Failing after 14m19s
Stop ITL regex from intercepting new messages before Agent execution; move scheduled task and schedule write confirmation to MCP tool calls with draft commit on 确认. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,102 +1,13 @@
|
||||
import { classifyUserIntent, inferActionLevel } from '../../intent-classifier.mjs';
|
||||
import { commitIntentDraft } from '../../intent-transaction-commit.mjs';
|
||||
import {
|
||||
formatDraftCancelledReply,
|
||||
formatDraftCommittedReply,
|
||||
formatIntentActionCard,
|
||||
parseDraftUserReply,
|
||||
} from '../../intent-action-card.mjs';
|
||||
import { formatQueryGuardReply } from '../../intent-query-guard.mjs';
|
||||
import { isIntentTransactionEnabled } from '../../intent-transaction-config.mjs';
|
||||
import { buildScheduledTaskCreatePayload } from '../../scheduled-task-intent.mjs';
|
||||
import { formatScheduledTaskCreateReply } from '../../scheduled-task-intent.mjs';
|
||||
import { isIntentTransactionEnabled } from '../../intent-transaction-config.mjs';
|
||||
import { isScheduledTaskWorkerEnabled, scheduledTaskWorkerDisabledMessage } from '../../scheduled-task-worker-config.mjs';
|
||||
|
||||
function buildDraftPayload(classification, { intent, user, timezone }) {
|
||||
const { kind, detail } = classification;
|
||||
const base = {
|
||||
sourceChannel: 'wechat',
|
||||
sourceMessageId: intent.msgId ?? null,
|
||||
sourceText: intent.agentText,
|
||||
timezone,
|
||||
};
|
||||
|
||||
if (kind === 'timed_reminder') {
|
||||
return {
|
||||
...base,
|
||||
title: detail.title,
|
||||
remindLocal: detail.remindLocal,
|
||||
hour: detail.hour,
|
||||
minute: detail.minute,
|
||||
recurrence: detail.recurrence ?? 'once',
|
||||
};
|
||||
}
|
||||
if (kind === 'create_todo') {
|
||||
return { ...base, title: detail.title };
|
||||
}
|
||||
if (kind === 'create_daily_todo_digest') {
|
||||
return { ...base, hour: detail.hour, minute: detail.minute ?? 0 };
|
||||
}
|
||||
if (kind === 'create_balance_alert') {
|
||||
return { ...base, thresholdCents: detail.thresholdCents };
|
||||
}
|
||||
if (kind === 'scheduled_task') {
|
||||
return {
|
||||
...base,
|
||||
intentDetail: detail,
|
||||
createPayload: buildScheduledTaskCreatePayload(detail, {
|
||||
userId: user.userId,
|
||||
sourceChannel: 'wechat',
|
||||
sourceMessageId: intent.msgId ?? null,
|
||||
sourceText: intent.agentText,
|
||||
timezone,
|
||||
}),
|
||||
};
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
function buildCard(classification, draftPayload, actionLevel, text) {
|
||||
const { layer, kind, detail, clarify } = classification;
|
||||
if (kind === 'clarify' || classification.layer === 'ambiguous') {
|
||||
return formatIntentActionCard({ clarify: ['notify_vs_act'] });
|
||||
}
|
||||
if (clarify?.length) {
|
||||
return formatIntentActionCard({ clarify });
|
||||
}
|
||||
|
||||
const title = detail?.title ?? detail?.taskSpec?.slice?.(0, 80) ?? draftPayload.title ?? '待确认任务';
|
||||
const trigger = detail?.remindLocal
|
||||
? { at: detail.remindLocal }
|
||||
: { hour: detail?.hour, minute: detail?.minute ?? 0 };
|
||||
const frequency = /(?:每天|每日)/u.test(text)
|
||||
? '每天自动执行'
|
||||
: detail?.recurrence === 'weekly'
|
||||
? '每周自动执行'
|
||||
: detail?.recurrence === 'daily'
|
||||
? '每天自动执行'
|
||||
: null;
|
||||
|
||||
const actions = kind === 'scheduled_task'
|
||||
? ['到点自动执行任务', '推送执行结果到微信']
|
||||
: kind === 'timed_reminder'
|
||||
? ['到点发送微信提醒']
|
||||
: kind === 'create_daily_todo_digest'
|
||||
? ['到点推送当天待办摘要']
|
||||
: kind === 'create_balance_alert'
|
||||
? ['余额低于阈值时发送微信提醒']
|
||||
: ['记录到待办列表'];
|
||||
|
||||
return formatIntentActionCard({
|
||||
title,
|
||||
layer,
|
||||
actionLevel,
|
||||
trigger,
|
||||
actions,
|
||||
frequency,
|
||||
});
|
||||
}
|
||||
|
||||
export async function handleWechatIntentTransaction({
|
||||
intent,
|
||||
user,
|
||||
@@ -114,108 +25,50 @@ export async function handleWechatIntentTransaction({
|
||||
try {
|
||||
const pending = await intentDraftService.getPendingDraft(user.userId);
|
||||
const replyKind = parseDraftUserReply(text);
|
||||
if (pending && replyKind) {
|
||||
if (replyKind === 'cancel') {
|
||||
await intentDraftService.cancelDraft(pending.id, user.userId);
|
||||
return formatDraftCancelledReply();
|
||||
}
|
||||
if (replyKind === 'modify') {
|
||||
await intentDraftService.cancelDraft(pending.id, user.userId, { reason: 'user_modify' });
|
||||
return '好的,请重新发送完整的安排,例如「下午 2 点半提醒我开项目计划例会」。';
|
||||
}
|
||||
if (replyKind === 'confirm') {
|
||||
if (pending.layer === 'ambiguous' || pending.draftType === 'clarify') {
|
||||
return '我还需要确认:你是要「到点提醒」还是「到点自动执行并交付结果」?请补充后再发「确认」。';
|
||||
}
|
||||
const committed = await commitIntentDraft({
|
||||
draft: pending,
|
||||
userId: user.userId,
|
||||
scheduleService,
|
||||
scheduledTaskService,
|
||||
taskUnifiedService,
|
||||
timezone,
|
||||
env,
|
||||
});
|
||||
await intentDraftService.markDraftCommitted(pending.id, user.userId, committed);
|
||||
if (committed.kind === 'scheduled_task') {
|
||||
const workerWarning = isScheduledTaskWorkerEnabled(env)
|
||||
? null
|
||||
: scheduledTaskWorkerDisabledMessage(env);
|
||||
let reply = formatScheduledTaskCreateReply(committed.task, { workerWarning });
|
||||
reply = `${reply}\n\n(已通过确认卡片写入)`;
|
||||
return reply;
|
||||
}
|
||||
return formatDraftCommittedReply({ title: pending.title, kind: pending.draftType });
|
||||
}
|
||||
if (!pending || !replyKind) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const classification = classifyUserIntent(text, { timezone });
|
||||
const { layer, kind, action } = classification;
|
||||
|
||||
if (layer === 'L0') {
|
||||
return formatQueryGuardReply(text, {
|
||||
if (replyKind === 'cancel') {
|
||||
await intentDraftService.cancelDraft(pending.id, user.userId);
|
||||
return formatDraftCancelledReply();
|
||||
}
|
||||
if (replyKind === 'modify') {
|
||||
await intentDraftService.cancelDraft(pending.id, user.userId, { reason: 'user_modify' });
|
||||
return '好的,请重新描述你的需求。';
|
||||
}
|
||||
if (replyKind === 'confirm') {
|
||||
if (pending.layer === 'ambiguous' || pending.draftType === 'clarify') {
|
||||
return '我还需要确认:你是要「到点提醒」还是「到点自动执行并交付结果」?请补充后再发「确认」。';
|
||||
}
|
||||
const committed = await commitIntentDraft({
|
||||
draft: pending,
|
||||
userId: user.userId,
|
||||
scheduleService,
|
||||
scheduledTaskService,
|
||||
taskUnifiedService,
|
||||
userId: user.userId,
|
||||
timezone,
|
||||
env,
|
||||
});
|
||||
}
|
||||
|
||||
if (layer === 'L3' || layer === null || kind === 'agent_schedule' || kind === 'agent_automation') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (kind === 'manage') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (layer === 'ambiguous' || kind === 'clarify') {
|
||||
if (classification.clarify?.length && kind !== 'clarify') {
|
||||
return buildCard(classification, {}, inferActionLevel(classification, text), text);
|
||||
await intentDraftService.markDraftCommitted(pending.id, user.userId, committed);
|
||||
if (committed.kind === 'scheduled_task') {
|
||||
const workerWarning = isScheduledTaskWorkerEnabled(env)
|
||||
? null
|
||||
: scheduledTaskWorkerDisabledMessage(env);
|
||||
let reply = formatScheduledTaskCreateReply(committed.task, { workerWarning });
|
||||
reply = `${reply}\n\n(已通过确认卡片写入)`;
|
||||
return reply;
|
||||
}
|
||||
await intentDraftService.createDraft({
|
||||
userId: user.userId,
|
||||
layer: 'ambiguous',
|
||||
draftType: 'clarify',
|
||||
actionLevel: 2,
|
||||
title: '待确认意图',
|
||||
payload: { clarify: ['notify_vs_act'], sourceText: text },
|
||||
cardText: buildCard({ layer: 'ambiguous', kind: 'clarify', clarify: ['notify_vs_act'] }, {}, 2, text),
|
||||
sourceMessageId: intent.msgId ?? null,
|
||||
sourceText: text,
|
||||
});
|
||||
return buildCard({ layer: 'ambiguous', kind: 'clarify', clarify: ['notify_vs_act'] }, {}, 2, text);
|
||||
if (committed.kind === 'schedule_item' || committed.kind === 'schedule_reminder') {
|
||||
return formatDraftCommittedReply({
|
||||
title: pending.title,
|
||||
kind: committed.kind === 'schedule_reminder' ? 'timed_reminder' : 'create_todo',
|
||||
});
|
||||
}
|
||||
return formatDraftCommittedReply({ title: pending.title, kind: pending.draftType });
|
||||
}
|
||||
|
||||
const draftableKinds = new Set([
|
||||
'timed_reminder',
|
||||
'create_todo',
|
||||
'create_daily_todo_digest',
|
||||
'create_balance_alert',
|
||||
'scheduled_task',
|
||||
]);
|
||||
if (!draftableKinds.has(kind)) return null;
|
||||
if (classification.clarify?.length) {
|
||||
return buildCard(classification, {}, inferActionLevel(classification, text), text);
|
||||
}
|
||||
|
||||
const actionLevel = inferActionLevel(classification, text);
|
||||
const payload = buildDraftPayload(classification, { intent, user, timezone });
|
||||
const cardText = buildCard(classification, payload, actionLevel, text);
|
||||
await intentDraftService.createDraft({
|
||||
userId: user.userId,
|
||||
layer,
|
||||
draftType: kind,
|
||||
actionLevel,
|
||||
title: payload.title ?? payload.createPayload?.title ?? classification.detail?.title ?? '待确认任务',
|
||||
payload,
|
||||
cardText,
|
||||
sourceMessageId: intent.msgId ?? null,
|
||||
sourceText: text,
|
||||
});
|
||||
return cardText;
|
||||
return null;
|
||||
} catch (err) {
|
||||
logger.warn?.(
|
||||
'[wechat-intent-transaction] failed:',
|
||||
|
||||
@@ -2,8 +2,8 @@ import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { handleWechatIntentTransaction } from './intent-transaction.mjs';
|
||||
|
||||
function createDraftStore() {
|
||||
let pending = null;
|
||||
function createDraftStore(initial = null) {
|
||||
let pending = initial;
|
||||
return {
|
||||
async getPendingDraft(userId) {
|
||||
return pending?.userId === userId ? pending : null;
|
||||
@@ -43,9 +43,6 @@ function createScheduleService() {
|
||||
async createReminder(payload) {
|
||||
return { id: 'reminder-1', ...payload };
|
||||
},
|
||||
buildTodoDigestText() {
|
||||
return '今天有 1 条待办。';
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -62,7 +59,7 @@ test('intent transaction returns null when feature disabled', async () => {
|
||||
assert.equal(reply, null);
|
||||
});
|
||||
|
||||
test('intent transaction creates action card for simple timed reminder', async () => {
|
||||
test('intent transaction passes through new messages to goose', async () => {
|
||||
const drafts = createDraftStore();
|
||||
const reply = await handleWechatIntentTransaction({
|
||||
intent: { agentText: '下午2点半提醒我开项目计划例会', msgId: 'm2' },
|
||||
@@ -71,14 +68,41 @@ test('intent transaction creates action card for simple timed reminder', async (
|
||||
scheduleService: createScheduleService(),
|
||||
env: enabledEnv,
|
||||
});
|
||||
assert.match(reply, /我准备执行/);
|
||||
assert.match(reply, /确认/);
|
||||
const pending = await drafts.getPendingDraft('user-1');
|
||||
assert.equal(pending.draftType, 'timed_reminder');
|
||||
assert.equal(reply, null);
|
||||
assert.equal(await drafts.getPendingDraft('user-1'), null);
|
||||
});
|
||||
|
||||
test('intent transaction commits draft after user confirms', async () => {
|
||||
const drafts = createDraftStore();
|
||||
test('intent transaction passes through page generation with auto refresh wording', async () => {
|
||||
const reply = await handleWechatIntentTransaction({
|
||||
intent: {
|
||||
agentText: '我想帮我做一个页面,页面上每天自动更新天气',
|
||||
msgId: 'm-page',
|
||||
},
|
||||
user: { userId: 'user-1' },
|
||||
intentDraftService: createDraftStore(),
|
||||
scheduleService: createScheduleService(),
|
||||
env: enabledEnv,
|
||||
});
|
||||
assert.equal(reply, null);
|
||||
});
|
||||
|
||||
test('intent transaction commits pending draft after user confirms', async () => {
|
||||
const drafts = createDraftStore({
|
||||
id: 'draft-1',
|
||||
userId: 'user-1',
|
||||
layer: 'L1',
|
||||
draftType: 'timed_reminder',
|
||||
title: '项目计划例会',
|
||||
status: 'draft',
|
||||
payload: {
|
||||
title: '项目计划例会',
|
||||
remindLocal: '2026-08-28 14:30',
|
||||
hour: 14,
|
||||
minute: 30,
|
||||
recurrence: 'once',
|
||||
sourceChannel: 'agent',
|
||||
},
|
||||
});
|
||||
const scheduleService = createScheduleService();
|
||||
const calls = [];
|
||||
scheduleService.createItem = async (payload) => {
|
||||
@@ -90,13 +114,6 @@ test('intent transaction commits draft after user confirms', async () => {
|
||||
return { id: 'reminder-1', ...payload };
|
||||
};
|
||||
|
||||
await handleWechatIntentTransaction({
|
||||
intent: { agentText: '下午2点半提醒我开项目计划例会', msgId: 'm3' },
|
||||
user: { userId: 'user-1' },
|
||||
intentDraftService: drafts,
|
||||
scheduleService,
|
||||
env: enabledEnv,
|
||||
});
|
||||
const reply = await handleWechatIntentTransaction({
|
||||
intent: { agentText: '确认', msgId: 'm4' },
|
||||
user: { userId: 'user-1' },
|
||||
@@ -108,28 +125,26 @@ test('intent transaction commits draft after user confirms', async () => {
|
||||
assert.equal(calls.length, 2);
|
||||
});
|
||||
|
||||
test('intent transaction asks for slot fill without creating draft', async () => {
|
||||
const drafts = createDraftStore();
|
||||
const reply = await handleWechatIntentTransaction({
|
||||
intent: { agentText: '设置提醒', msgId: 'm6' },
|
||||
user: { userId: 'user-2' },
|
||||
intentDraftService: drafts,
|
||||
scheduleService: createScheduleService(),
|
||||
env: enabledEnv,
|
||||
test('intent transaction cancels pending draft', async () => {
|
||||
const drafts = createDraftStore({
|
||||
id: 'draft-1',
|
||||
userId: 'user-1',
|
||||
layer: 'L2',
|
||||
draftType: 'agent_tool',
|
||||
title: '定时任务',
|
||||
status: 'draft',
|
||||
payload: {
|
||||
toolName: 'scheduled_task_create',
|
||||
toolArgs: { taskSpec: '做新闻页面', hour: 6 },
|
||||
},
|
||||
});
|
||||
assert.match(reply, /补充/);
|
||||
assert.equal(await drafts.getPendingDraft('user-2'), null);
|
||||
});
|
||||
|
||||
test('intent transaction answers query guard without creating draft', async () => {
|
||||
const drafts = createDraftStore();
|
||||
const reply = await handleWechatIntentTransaction({
|
||||
intent: { agentText: '看看我的待办', msgId: 'm5' },
|
||||
intent: { agentText: '取消', msgId: 'm5' },
|
||||
user: { userId: 'user-1' },
|
||||
intentDraftService: drafts,
|
||||
scheduleService: createScheduleService(),
|
||||
env: enabledEnv,
|
||||
});
|
||||
assert.match(reply, /待办/);
|
||||
assert.match(reply, /已取消/);
|
||||
assert.equal(await drafts.getPendingDraft('user-1'), null);
|
||||
});
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import {
|
||||
buildScheduledTaskCreatePayload,
|
||||
formatScheduledTaskClarification,
|
||||
formatScheduledTaskCreateReply,
|
||||
formatScheduledTaskListReply,
|
||||
isScheduledTaskIntent,
|
||||
parseScheduledTaskIntent,
|
||||
} from '../../scheduled-task-intent.mjs';
|
||||
import {
|
||||
isScheduledTaskWorkerEnabled,
|
||||
scheduledTaskWorkerDisabledMessage,
|
||||
} from '../../scheduled-task-worker-config.mjs';
|
||||
|
||||
const WEEKDAY_NAMES = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||
|
||||
@@ -53,28 +46,7 @@ export async function handleWechatScheduledTaskIntent({
|
||||
}
|
||||
|
||||
if (taskIntent.action === 'create_scheduled_task') {
|
||||
if (taskIntent.needsClarification?.length) {
|
||||
return formatScheduledTaskClarification(taskIntent);
|
||||
}
|
||||
const payload = buildScheduledTaskCreatePayload(taskIntent, {
|
||||
userId: user.userId,
|
||||
sourceChannel: 'wechat',
|
||||
sourceMessageId: intent.msgId || null,
|
||||
sourceText: intent.agentText,
|
||||
timezone,
|
||||
});
|
||||
const task = await scheduledTaskService.createTask(payload);
|
||||
const workerWarning = isScheduledTaskWorkerEnabled(env)
|
||||
? null
|
||||
: scheduledTaskWorkerDisabledMessage(env);
|
||||
let reply = formatScheduledTaskCreateReply(task, { workerWarning });
|
||||
if (task.recurrence === 'weekly' && task.weekday != null) {
|
||||
reply = reply.replace(
|
||||
/执行时间:/,
|
||||
`执行时间:${WEEKDAY_NAMES[Number(task.weekday)] ?? ''} `,
|
||||
);
|
||||
}
|
||||
return reply;
|
||||
return null;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn?.(
|
||||
|
||||
@@ -2,8 +2,7 @@ import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { handleWechatScheduledTaskIntent } from './scheduled-task.mjs';
|
||||
|
||||
test('handleWechatScheduledTaskIntent creates daily automation task', async () => {
|
||||
const created = [];
|
||||
test('handleWechatScheduledTaskIntent passes create requests to goose', async () => {
|
||||
const reply = await handleWechatScheduledTaskIntent({
|
||||
intent: {
|
||||
msgType: 'text',
|
||||
@@ -12,45 +11,15 @@ test('handleWechatScheduledTaskIntent creates daily automation task', async () =
|
||||
},
|
||||
user: { userId: 'user-1' },
|
||||
scheduledTaskService: {
|
||||
async createTask(input) {
|
||||
created.push(input);
|
||||
return {
|
||||
...input,
|
||||
id: 'task-1',
|
||||
nextRunAt: Date.now() + 3600_000,
|
||||
};
|
||||
async createTask() {
|
||||
throw new Error('should not create from inbound regex');
|
||||
},
|
||||
},
|
||||
env: { H5_SCHEDULED_TASK_WORKER_ENABLED: '1' },
|
||||
});
|
||||
|
||||
assert.equal(created.length, 1);
|
||||
assert.equal(created[0].hour, 6);
|
||||
assert.match(created[0].taskSpec, /今日新闻页面/u);
|
||||
assert.match(reply, /已设置每天定时任务/u);
|
||||
assert.equal(reply, null);
|
||||
});
|
||||
|
||||
test('handleWechatScheduledTaskIntent warns when worker disabled', async () => {
|
||||
const reply = await handleWechatScheduledTaskIntent({
|
||||
intent: {
|
||||
msgType: 'text',
|
||||
agentText: '每天6点帮我做今日新闻页面',
|
||||
msgId: 'msg-1',
|
||||
},
|
||||
user: { userId: 'user-1' },
|
||||
scheduledTaskService: {
|
||||
async createTask(input) {
|
||||
return { ...input, id: 'task-1', nextRunAt: Date.now() + 3600_000 };
|
||||
},
|
||||
},
|
||||
env: { H5_SCHEDULED_TASK_WORKER_ENABLED: '0', H5_REMINDER_WORKER_ENABLED: '0' },
|
||||
});
|
||||
|
||||
assert.match(reply, /⚠️/u);
|
||||
assert.match(reply, /未开启定时自动执行 Worker/u);
|
||||
});
|
||||
|
||||
test('handleWechatScheduledTaskIntent returns clarification for incomplete request', async () => {
|
||||
test('handleWechatScheduledTaskIntent passes incomplete create requests to goose', async () => {
|
||||
const reply = await handleWechatScheduledTaskIntent({
|
||||
intent: {
|
||||
msgType: 'text',
|
||||
@@ -60,12 +29,11 @@ test('handleWechatScheduledTaskIntent returns clarification for incomplete reque
|
||||
user: { userId: 'user-1' },
|
||||
scheduledTaskService: {
|
||||
async createTask() {
|
||||
throw new Error('should not create');
|
||||
throw new Error('should not create from inbound regex');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.match(reply, /执行时间和任务内容/u);
|
||||
assert.equal(reply, null);
|
||||
});
|
||||
|
||||
test('handleWechatScheduledTaskIntent ignores non automation text', async () => {
|
||||
@@ -84,3 +52,20 @@ test('handleWechatScheduledTaskIntent ignores non automation text', async () =>
|
||||
});
|
||||
assert.equal(reply, null);
|
||||
});
|
||||
|
||||
test('handleWechatScheduledTaskIntent still lists tasks', async () => {
|
||||
const reply = await handleWechatScheduledTaskIntent({
|
||||
intent: {
|
||||
msgType: 'text',
|
||||
agentText: '看看我的定时自动任务',
|
||||
msgId: 'msg-1',
|
||||
},
|
||||
user: { userId: 'user-1' },
|
||||
scheduledTaskService: {
|
||||
async listTasks() {
|
||||
return [{ title: '新闻页面', recurrence: 'daily', hour: 6, minute: 0, timezone: 'Asia/Shanghai' }];
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.match(reply, /新闻页面/u);
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
isWechatPageEditText,
|
||||
isWechatPageRetryText,
|
||||
} from './page-continuation.mjs';
|
||||
import { shouldUseScheduledTaskAutomation } from '../../scheduled-task-intent.mjs';
|
||||
|
||||
export const PAGE_GENERATE_PATTERN =
|
||||
/(?:生成|创建|做|写|帮我.*(?:生成|创建|做|写)).*(?:html|页面|网页|page|文件)/iu;
|
||||
@@ -39,7 +38,6 @@ export const CONNECTIVITY_TEST_PATTERN = /^(测试\s*\d*|test\s*\d*)[!!。.\s]
|
||||
export function isPageGenerateText(text) {
|
||||
const normalized = String(text ?? '').trim();
|
||||
if (!normalized) return false;
|
||||
if (shouldUseScheduledTaskAutomation(normalized)) return false;
|
||||
if (PAGE_GENERATE_NEGATION_PATTERN.test(normalized)) return false;
|
||||
return (
|
||||
PAGE_GENERATE_PATTERN.test(normalized)
|
||||
|
||||
Reference in New Issue
Block a user