32fb2cdeaf
Add chat file/image upload UX, attachment proxying, vision thumbnails, and per-turn image scoping so agents only use the current upload. Extend MindSpace asset context, billing token state, OA/scenario verify scripts, and related runtime config. Co-authored-by: Cursor <cursoragent@cursor.com>
173 lines
5.5 KiB
JavaScript
173 lines
5.5 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* 验证 john4 儿童爱好问卷「饮食偏好 / 每日几顿」增量改造链路(无需重新聊天)。
|
||
*
|
||
* Usage:
|
||
* node scripts/verify-children-hobby-diet-survey.mjs
|
||
* node scripts/verify-children-hobby-diet-survey.mjs --no-insert
|
||
* node scripts/verify-children-hobby-diet-survey.mjs --user-id <uuid> --port 8081
|
||
*/
|
||
import { execSync } from 'node:child_process';
|
||
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
import { loadH5Environment } from './load-env.mjs';
|
||
import {
|
||
CHILDREN_HOBBY_DIET_SURVEY,
|
||
createReporter,
|
||
resolvePortalBase,
|
||
verifyChildrenHobbyDietSurvey,
|
||
} from './scenario-test-lib.mjs';
|
||
import { PUBLISH_ROOT_DIR } from '../user-publish.mjs';
|
||
import { createDbPool } from '../db.mjs';
|
||
|
||
const repoRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||
|
||
loadH5Environment(import.meta.dirname);
|
||
|
||
function parseArgs(argv) {
|
||
const options = {
|
||
userId: CHILDREN_HOBBY_DIET_SURVEY.userId,
|
||
port: Number(process.env.H5_PORT ?? 8081),
|
||
testInsert: true,
|
||
checkRuns: true,
|
||
};
|
||
for (let i = 2; i < argv.length; i += 1) {
|
||
const arg = argv[i];
|
||
if (arg === '--no-insert') options.testInsert = false;
|
||
else if (arg === '--no-runs') options.checkRuns = false;
|
||
else if (arg === '--user-id' && argv[i + 1]) options.userId = argv[++i];
|
||
else if (arg === '--port' && argv[i + 1]) options.port = Number(argv[++i]);
|
||
else if (arg === '-h' || arg === '--help') {
|
||
console.log(`Usage: node scripts/verify-children-hobby-diet-survey.mjs [--no-insert] [--no-runs] [--user-id <uuid>] [--port <port>]`);
|
||
process.exit(0);
|
||
} else {
|
||
throw new Error(`未知参数: ${arg}`);
|
||
}
|
||
}
|
||
return options;
|
||
}
|
||
|
||
async function ensurePortalReady(baseUrl) {
|
||
const response = await fetch(`${baseUrl}/auth/status`);
|
||
if (!response.ok) {
|
||
throw new Error(`Portal 未就绪: ${baseUrl}/auth/status -> ${response.status}`);
|
||
}
|
||
}
|
||
|
||
function verifySqliteColumn(sqlitePath, tableName, columnName, reporter) {
|
||
try {
|
||
const output = execSync(
|
||
`sqlite3 ${JSON.stringify(sqlitePath)} "PRAGMA table_info(${tableName});"`,
|
||
{ encoding: 'utf8' },
|
||
);
|
||
if (!output.includes(`|${columnName}|`)) {
|
||
reporter.fail('SQLite 列', `${tableName}.${columnName} 不存在`);
|
||
return false;
|
||
}
|
||
reporter.pass('SQLite 列', `${tableName}.${columnName}`);
|
||
const datasetJson = execSync(
|
||
`sqlite3 ${JSON.stringify(sqlitePath)} "SELECT config_json FROM __page_data_datasets WHERE name='${CHILDREN_HOBBY_DIET_SURVEY.dataset}';"`,
|
||
{ encoding: 'utf8' },
|
||
).trim();
|
||
const config = JSON.parse(datasetJson);
|
||
const insertCols = config?.columns?.insert ?? [];
|
||
if (!insertCols.includes(columnName)) {
|
||
reporter.fail('dataset insert 列', `${columnName} 不在 ${insertCols.join(', ')}`);
|
||
return false;
|
||
}
|
||
reporter.pass('dataset insert 列', insertCols.join(', '));
|
||
return true;
|
||
} catch (error) {
|
||
reporter.fail('SQLite 检查', error instanceof Error ? error.message : String(error));
|
||
return false;
|
||
}
|
||
}
|
||
|
||
async function verifyAgentRunHealth(userId, reporter) {
|
||
if (!process.env.DATABASE_URL && !fs.existsSync(path.join(repoRoot, '.env'))) {
|
||
reporter.pass('Agent run 健康', '跳过(无 DATABASE_URL)');
|
||
return true;
|
||
}
|
||
try {
|
||
for (const line of fs.readFileSync(path.join(repoRoot, '.env'), 'utf8').split('\n')) {
|
||
const t = line.trim();
|
||
if (!t || t.startsWith('#')) continue;
|
||
const eq = t.indexOf('=');
|
||
if (eq < 0) continue;
|
||
const k = t.slice(0, eq).trim();
|
||
const v = t.slice(eq + 1).trim();
|
||
if (!process.env[k]) process.env[k] = v;
|
||
}
|
||
const pool = createDbPool();
|
||
const [runningRows] = await pool.query(
|
||
`SELECT id, agent_session_id, updated_at
|
||
FROM h5_agent_runs
|
||
WHERE user_id = ? AND status = 'running'
|
||
ORDER BY updated_at DESC
|
||
LIMIT 5`,
|
||
[userId],
|
||
);
|
||
await pool.end();
|
||
if (runningRows.length > 0) {
|
||
reporter.fail('Agent run 健康', `${runningRows.length} 条 running(可能前端仍在 loading)`);
|
||
for (const row of runningRows) {
|
||
console.error(` - ${row.id} session=${row.agent_session_id}`);
|
||
}
|
||
return false;
|
||
}
|
||
reporter.pass('Agent run 健康', '无 orphan running');
|
||
return true;
|
||
} catch (error) {
|
||
reporter.fail('Agent run 健康', error instanceof Error ? error.message : String(error));
|
||
return false;
|
||
}
|
||
}
|
||
|
||
async function main() {
|
||
const options = parseArgs(process.argv);
|
||
const reporter = createReporter();
|
||
const baseUrl = resolvePortalBase(options.port);
|
||
const publishKey = options.userId;
|
||
const sqlitePath = path.join(
|
||
repoRoot,
|
||
PUBLISH_ROOT_DIR,
|
||
publishKey,
|
||
'.mindspace',
|
||
'private-data.sqlite',
|
||
);
|
||
|
||
console.log('==> 儿童爱好问卷 · 饮食偏好链路验证');
|
||
console.log(` Portal: ${baseUrl}`);
|
||
console.log(` 用户: ${publishKey}\n`);
|
||
|
||
await ensurePortalReady(baseUrl);
|
||
|
||
let ok = true;
|
||
ok = verifySqliteColumn(
|
||
sqlitePath,
|
||
CHILDREN_HOBBY_DIET_SURVEY.dataset,
|
||
CHILDREN_HOBBY_DIET_SURVEY.dietField,
|
||
reporter,
|
||
) && ok;
|
||
|
||
ok = await verifyChildrenHobbyDietSurvey({
|
||
publishKey,
|
||
baseUrl,
|
||
reporter,
|
||
testInsert: options.testInsert,
|
||
}) && ok;
|
||
|
||
if (options.checkRuns) {
|
||
ok = await verifyAgentRunHealth(publishKey, reporter) && ok;
|
||
}
|
||
|
||
const code = reporter.summary();
|
||
process.exit(code);
|
||
}
|
||
|
||
main().catch((error) => {
|
||
console.error(error instanceof Error ? error.stack ?? error.message : error);
|
||
process.exit(1);
|
||
});
|