Add per-feature Cursor channel toggles for admin and runtime.
Memind CI / Test, build, and release guards (push) Has been cancelled
Memind CI / Test, build, and release guards (push) Has been cancelled
Expose page/data/scheduled-task/chat-bridge switches in the智趣体验通道 config so Tang can roll out Cursor paths independently with DeepSeek fallback. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 将唐用户智趣体验通道更新为推荐配置(103 生产库)
|
||||
* 默认 dry-run;--apply 才写入 h5_wechat_cursor_executor_config
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/apply-tang-cursor-config-103.mjs
|
||||
* node scripts/apply-tang-cursor-config-103.mjs --apply
|
||||
*/
|
||||
import process from 'node:process';
|
||||
import { execSync } from 'node:child_process';
|
||||
import mysql from 'mysql2/promise';
|
||||
import {
|
||||
CURSOR_CHANNEL_FEATURES,
|
||||
defaultCursorChannelFeatures,
|
||||
intentAllowlistFromFeatures,
|
||||
} from '../cursor-channel-features.mjs';
|
||||
|
||||
const TANG = 'a70ff537-8908-486e-9b6c-042e07cc25db';
|
||||
|
||||
const RECOMMENDED = {
|
||||
enabled: true,
|
||||
userAllowlist: [TANG],
|
||||
channelAllowlist: ['h5', 'wechat_mp'],
|
||||
features: {
|
||||
...defaultCursorChannelFeatures(),
|
||||
[CURSOR_CHANNEL_FEATURES.PAGE_GENERATE]: { enabled: true },
|
||||
[CURSOR_CHANNEL_FEATURES.PAGE_DATA]: { enabled: true },
|
||||
[CURSOR_CHANNEL_FEATURES.EXCEL_ANALYSIS]: { enabled: false },
|
||||
[CURSOR_CHANNEL_FEATURES.CHAT_BRIDGE]: { enabled: false },
|
||||
[CURSOR_CHANNEL_FEATURES.SCHEDULED_TASKS]: { enabled: true },
|
||||
},
|
||||
fallbackToDeepseek: true,
|
||||
meta: { notes: '唐用户 Cursor 灰度 — 页面+问卷+定时任务' },
|
||||
};
|
||||
|
||||
RECOMMENDED.intentAllowlist = intentAllowlistFromFeatures(RECOMMENDED.features);
|
||||
|
||||
async function main() {
|
||||
const apply = process.argv.includes('--apply');
|
||||
const databaseUrl = execSync(
|
||||
"ssh -o BatchMode=yes -o ConnectTimeout=8 john@58.38.22.103 \"grep '^DATABASE_URL=' /Users/john/Project/Memind/.env | cut -d= -f2-\"",
|
||||
{ encoding: 'utf8' },
|
||||
).trim();
|
||||
const pool = mysql.createPool({ uri: databaseUrl, connectionLimit: 2 });
|
||||
const now = Date.now();
|
||||
|
||||
const [before] = await pool.query(
|
||||
'SELECT config_json FROM h5_wechat_cursor_executor_config WHERE config_scope = ? LIMIT 1',
|
||||
['global'],
|
||||
);
|
||||
console.log('--- before ---');
|
||||
console.log(JSON.stringify(before[0]?.config_json ?? null, null, 2));
|
||||
console.log('--- target ---');
|
||||
console.log(JSON.stringify(RECOMMENDED, null, 2));
|
||||
|
||||
if (!apply) {
|
||||
console.log('\n(dry-run) 追加 --apply 写入生产库');
|
||||
await pool.end();
|
||||
return;
|
||||
}
|
||||
|
||||
await pool.query(
|
||||
`INSERT INTO h5_wechat_cursor_executor_config
|
||||
(config_scope, config_json, updated_by, updated_at)
|
||||
VALUES ('global', ?, NULL, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
config_json = VALUES(config_json),
|
||||
updated_at = VALUES(updated_at)`,
|
||||
[JSON.stringify(RECOMMENDED), now],
|
||||
);
|
||||
|
||||
const [after] = await pool.query(
|
||||
'SELECT config_json, updated_at FROM h5_wechat_cursor_executor_config WHERE config_scope = ? LIMIT 1',
|
||||
['global'],
|
||||
);
|
||||
console.log('\n--- applied ---');
|
||||
console.log(JSON.stringify(after[0] ?? null, null, 2));
|
||||
await pool.end();
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -12,8 +12,8 @@ loadH5Environment(import.meta.dirname);
|
||||
|
||||
const TANG = 'a70ff537-8908-486e-9b6c-042e07cc25db';
|
||||
const DAILY_TASK_IDS = [
|
||||
'21799936-0532-420c-b144-65ea3846cde1', // 5:30 news
|
||||
'23456db0-918e-4055-9ca3-e17e83b2dc24', // 8:00 weather
|
||||
'816dc9a2-5304-4e45-bb29-667143997362', // 5:30 每日新闻早报页面
|
||||
'23456db0-918e-4055-9ca3-e17e83b2dc24', // 8:00 每日天气预报(上海+武穴)
|
||||
];
|
||||
|
||||
const fmt = (ms) => new Date(Number(ms)).toLocaleString('zh-CN', {
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 唐用户智趣通道 103 只读巡检 + 推荐配置输出
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/verify-tang-cursor-channel-103.mjs
|
||||
* node scripts/verify-tang-cursor-channel-103.mjs --print-recommended-patch
|
||||
*/
|
||||
import process from 'node:process';
|
||||
import { execSync } from 'node:child_process';
|
||||
import mysql from 'mysql2/promise';
|
||||
import {
|
||||
CURSOR_CHANNEL_FEATURES,
|
||||
defaultCursorChannelFeatures,
|
||||
normalizeCursorChannelFeatures,
|
||||
} from '../cursor-channel-features.mjs';
|
||||
|
||||
const TANG = 'a70ff537-8908-486e-9b6c-042e07cc25db';
|
||||
const TANG_USERNAME = 'wx_ul610et8';
|
||||
|
||||
const RECOMMENDED_FEATURES = {
|
||||
...defaultCursorChannelFeatures(),
|
||||
[CURSOR_CHANNEL_FEATURES.PAGE_GENERATE]: { enabled: true },
|
||||
[CURSOR_CHANNEL_FEATURES.PAGE_DATA]: { enabled: true },
|
||||
[CURSOR_CHANNEL_FEATURES.EXCEL_ANALYSIS]: { enabled: false },
|
||||
[CURSOR_CHANNEL_FEATURES.CHAT_BRIDGE]: { enabled: false },
|
||||
[CURSOR_CHANNEL_FEATURES.SCHEDULED_TASKS]: { enabled: true },
|
||||
};
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const printPatch = args.includes('--print-recommended-patch');
|
||||
|
||||
function pass(label, detail = '') {
|
||||
console.log(`✔ ${label}${detail ? `: ${detail}` : ''}`);
|
||||
}
|
||||
|
||||
function warn(label, detail = '') {
|
||||
console.warn(`⚠ ${label}${detail ? `: ${detail}` : ''}`);
|
||||
}
|
||||
|
||||
function fail(label, detail = '') {
|
||||
console.error(`✘ ${label}${detail ? `: ${detail}` : ''}`);
|
||||
}
|
||||
|
||||
async function loadProdDatabaseUrl() {
|
||||
return execSync(
|
||||
"ssh -o BatchMode=yes -o ConnectTimeout=8 john@58.38.22.103 \"grep '^DATABASE_URL=' /Users/john/Project/Memind/.env | cut -d= -f2-\"",
|
||||
{ encoding: 'utf8' },
|
||||
).trim();
|
||||
}
|
||||
|
||||
async function read103Env(pattern) {
|
||||
try {
|
||||
const out = execSync(
|
||||
`ssh -o BatchMode=yes -o ConnectTimeout=8 john@58.38.22.103 'grep -E "${pattern}" /Users/john/Project/Memind/.env || true'`,
|
||||
{ encoding: 'utf8' },
|
||||
).trim();
|
||||
return out.split('\n').filter(Boolean);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let failed = 0;
|
||||
const databaseUrl = await loadProdDatabaseUrl();
|
||||
const pool = mysql.createPool({ uri: databaseUrl, connectionLimit: 2 });
|
||||
|
||||
const [users] = await pool.query(
|
||||
'SELECT id, username, display_name FROM h5_users WHERE id = ? LIMIT 1',
|
||||
[TANG],
|
||||
);
|
||||
const user = users[0];
|
||||
if (user?.username === TANG_USERNAME) pass('唐用户身份', `${user.display_name} / ${user.username}`);
|
||||
else { fail('唐用户身份'); failed += 1; }
|
||||
|
||||
const envLines = await read103Env('^(MEMIND_CURSOR|MEMIND_CURSOR_CHAT_BRIDGE|H5_SCHEDULED_TASK)');
|
||||
const envMap = Object.fromEntries(
|
||||
envLines.map((line) => {
|
||||
const idx = line.indexOf('=');
|
||||
return [line.slice(0, idx), line.slice(idx + 1)];
|
||||
}),
|
||||
);
|
||||
|
||||
if (envMap.MEMIND_CURSOR_EXECUTOR_ENABLED === '1') pass('Cursor 基础设施', 'MEMIND_CURSOR_EXECUTOR_ENABLED=1');
|
||||
else { fail('Cursor 基础设施', '未开启 MEMIND_CURSOR_EXECUTOR_ENABLED'); failed += 1; }
|
||||
|
||||
if (envMap.MEMIND_CURSOR_CHAT_BRIDGE_ENABLED === '1') {
|
||||
pass('Chat Bridge 基础设施', '已配置');
|
||||
} else {
|
||||
warn('Chat Bridge 基础设施', '未开 MEMIND_CURSOR_CHAT_BRIDGE_ENABLED;后台 chatBridge 开关暂不会生效');
|
||||
}
|
||||
|
||||
if (envMap.H5_SCHEDULED_TASK_WORKER_ENABLED === '1') pass('定时任务 Worker', '已开启');
|
||||
else { warn('定时任务 Worker', 'H5_SCHEDULED_TASK_WORKER_ENABLED 未开'); }
|
||||
|
||||
const [cursorRows] = await pool.query(
|
||||
'SELECT config_json FROM h5_wechat_cursor_executor_config WHERE config_scope = ? LIMIT 1',
|
||||
['global'],
|
||||
);
|
||||
const stored = cursorRows[0]?.config_json ?? null;
|
||||
const parsed = typeof stored === 'string' ? JSON.parse(stored) : (stored ?? {});
|
||||
const features = normalizeCursorChannelFeatures(parsed.features, {
|
||||
intentAllowlist: parsed.intentAllowlist,
|
||||
});
|
||||
|
||||
if (parsed.enabled) pass('智趣总开关', 'enabled');
|
||||
else { warn('智趣总开关', '当前关闭'); }
|
||||
|
||||
const allowlist = Array.isArray(parsed.userAllowlist) ? parsed.userAllowlist : [];
|
||||
if (allowlist.map((v) => v.toLowerCase()).includes(TANG.toLowerCase())) {
|
||||
pass('唐在白名单', `${allowlist.length} 人`);
|
||||
} else {
|
||||
fail('唐在白名单', `当前: ${allowlist.join(', ') || '(空)'}`);
|
||||
failed += 1;
|
||||
}
|
||||
|
||||
for (const [key, label] of [
|
||||
[CURSOR_CHANNEL_FEATURES.PAGE_GENERATE, '页面生成'],
|
||||
[CURSOR_CHANNEL_FEATURES.PAGE_DATA, '问卷 Page Data'],
|
||||
[CURSOR_CHANNEL_FEATURES.SCHEDULED_TASKS, '定时任务 Cursor'],
|
||||
[CURSOR_CHANNEL_FEATURES.CHAT_BRIDGE, '普通聊天 Bridge'],
|
||||
]) {
|
||||
const on = features[key]?.enabled === true;
|
||||
const want = RECOMMENDED_FEATURES[key]?.enabled === true;
|
||||
if (on === want) pass(`能力 ${label}`, on ? '已开' : '已关(符合推荐)');
|
||||
else warn(`能力 ${label}`, `当前=${on ? '开' : '关'},推荐=${want ? '开' : '关'}`);
|
||||
}
|
||||
|
||||
const [activeTasks] = await pool.query(
|
||||
`SELECT COUNT(*) AS c FROM h5_scheduled_tasks WHERE user_id = ? AND status IN ('active','locked')`,
|
||||
[TANG],
|
||||
);
|
||||
const activeCount = Number(activeTasks[0]?.c ?? 0);
|
||||
if (activeCount > 0) pass('active 定时任务', String(activeCount));
|
||||
else warn('active 定时任务', '0 — 需重建新闻/天气任务后才能验证 scheduledTasks');
|
||||
|
||||
const [failedTasks] = await pool.query(
|
||||
`SELECT title, last_error FROM h5_scheduled_tasks
|
||||
WHERE user_id = ? AND status = 'failed'
|
||||
ORDER BY updated_at DESC LIMIT 5`,
|
||||
[TANG],
|
||||
);
|
||||
if (failedTasks.length) {
|
||||
console.log('\n--- 最近 failed 定时任务 ---');
|
||||
for (const row of failedTasks) {
|
||||
console.log(`- ${row.title}: ${String(row.last_error ?? '').slice(0, 120)}`);
|
||||
}
|
||||
}
|
||||
|
||||
const recommendedPatch = {
|
||||
enabled: true,
|
||||
userAllowlist: [TANG],
|
||||
channelAllowlist: ['h5', 'wechat_mp'],
|
||||
features: RECOMMENDED_FEATURES,
|
||||
fallbackToDeepseek: true,
|
||||
};
|
||||
|
||||
if (printPatch) {
|
||||
console.log('\n--- 推荐 memind_adm 保存 payload ---');
|
||||
console.log(JSON.stringify(recommendedPatch, null, 2));
|
||||
} else {
|
||||
console.log('\n提示: node scripts/verify-tang-cursor-channel-103.mjs --print-recommended-patch');
|
||||
}
|
||||
|
||||
await pool.end();
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user