| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- #!/usr/bin/env node
- const fs = require('fs');
- const path = require('path');
- const { readTihaoToken, readEvidenceProviderToken, readSocialAnalysisToken } = require('../mcp/src/core/credentials');
- const { resolveTihaoUserInfo } = require('../mcp/src/core/payment-links');
- const { redactSecrets } = require('../mcp/src/core/result-envelope');
- async function main() {
- const args = parseArgs(process.argv.slice(2));
- const strict = Boolean(args.strict);
- const allowFailure = Boolean(args.allowFailure || args.allowFail);
- const outputRoot = path.resolve(args.output || args.outputRoot || process.env.TIHAO_PREFLIGHT_OUTPUT || path.join(__dirname, '..', 'outputs', `live-preflight-${Date.now()}`));
- fs.mkdirSync(outputRoot, { recursive: true });
- const token = readTihaoToken({});
- const socialAnalysisToken = readSocialAnalysisToken({});
- const evidenceToken = readEvidenceProviderToken({});
- const companyFromEnv = process.env.TIHAO_COMPANY || process.env.VOC_ECOMMERCE_COMPANY || process.env.COMPANY_OBJECT_ID || '';
- const baseUrl = process.env.TIHAO_ECOMMERCE_BASE_URL || 'https://server.fmode.cn/api/voc-e-commerce';
- const referenceBaseUrl = process.env.SOCIAL_ANALYSIS_BASE_URL || process.env.TIHAO_REFERENCE_ENRICHMENT_BASE_URL || '';
- const homepageBaseUrl = process.env.TIHAO_HOMEPAGE_EVIDENCE_BASE_URL || process.env.HOMEPAGE_EVIDENCE_BASE_URL || '';
- const evidenceBaseUrl = process.env.TIHAO_EVIDENCE_BASE_URL || process.env.MULTIMODAL_EVIDENCE_BASE_URL || '';
- const videoBaseUrl = process.env.VIDEO_ANALYSIS_BASE_URL || process.env.DOUBAO_VISION_BASE_URL || '';
- const videoModel = process.env.VIDEO_ANALYSIS_MODEL || process.env.DOUBAO_VISION_MODEL || 'doubao-seed-2-0-pro-260215';
- const checks = [];
- checks.push(check('session-token-present', Boolean(token), '需要 TIHAO_SESSION_TOKEN / VOC_ECOMMERCE_TOKEN / VOC_TOKEN 才能跑真实 live。'));
- checks.push(check('live-base-url-present', Boolean(baseUrl), '需要 live provider base url。'));
- checks.push(check('reference-provider-configured', Boolean(referenceBaseUrl), '未配置参考账号补证 provider;可先跑 brief-only/result-first,但不能证明参考链路真实接入。', 'warn'));
- checks.push(check('homepage-provider-configured', Boolean(homepageBaseUrl), '未配置主页最近内容 provider;主页证据会使用 fallback/已有标签。', 'warn'));
- checks.push(check('evidence-or-video-provider-configured', Boolean(evidenceBaseUrl || videoBaseUrl), '未配置多模态 evidence 或豆包视频分析 provider;不能证明视频证据提升。', 'warn'));
- checks.push(check('video-model-doubao-seed-2-0-pro-260215', !videoBaseUrl || videoModel === 'doubao-seed-2-0-pro-260215', `当前视频模型为 ${videoModel},用户指定模型应为 doubao-seed-2-0-pro-260215。`));
- checks.push(check('reference-token-present-if-provider', !referenceBaseUrl || Boolean(socialAnalysisToken || token), '参考账号补证 provider 已配置,但没有 VOC_SOCIAL_TOKEN/sessionToken。'));
- checks.push(check('evidence-token-present-if-provider', !evidenceBaseUrl && !videoBaseUrl || Boolean(evidenceToken), '证据/视频 provider 已配置,但没有 TIHAO_EVIDENCE_TOKEN / VIDEO_ANALYSIS_TOKEN / DOUBAO_VISION_TOKEN。'));
- let identity = null;
- if (token) {
- try {
- identity = await resolveTihaoUserInfo(token);
- checks.push(check('parse-user-resolved', Boolean(identity && identity.userId), 'Parse /users/me 未返回可识别用户。'));
- } catch (error) {
- checks.push(check('parse-user-resolved', false, `Parse 用户解析失败:${safeMessage(error)}`));
- }
- }
- const company = companyFromEnv || identity?.companyId || '';
- checks.push(check('company-resolved', Boolean(company), '需要 TIHAO_COMPANY / VOC_ECOMMERCE_COMPANY / COMPANY_OBJECT_ID,或 sessionToken 用户上有 company。'));
- const strictFailures = checks.filter(item => item.status === 'fail');
- const warnings = checks.filter(item => item.status === 'warn');
- const readyForLiveAcceptance = strictFailures.length === 0;
- const readyForVideoAb = readyForLiveAcceptance && Boolean(videoBaseUrl && evidenceToken);
- const summary = {
- generatedAt: new Date().toISOString(),
- readyForLiveAcceptance,
- readyForVideoAb,
- strict,
- allowFailure,
- live: {
- baseUrl,
- tokenPresent: Boolean(token),
- companyResolved: Boolean(company),
- parseUserResolved: Boolean(identity && identity.userId)
- },
- providers: {
- referenceConfigured: Boolean(referenceBaseUrl),
- homepageConfigured: Boolean(homepageBaseUrl),
- evidenceConfigured: Boolean(evidenceBaseUrl),
- videoConfigured: Boolean(videoBaseUrl),
- videoModel
- },
- checks,
- nextActions: buildNextActions({ readyForLiveAcceptance, readyForVideoAb, warnings, strictFailures })
- };
- const summaryPath = path.join(outputRoot, 'live-preflight-summary.json');
- fs.writeFileSync(summaryPath, JSON.stringify(summary, null, 2), 'utf8');
- console.log(JSON.stringify({
- outputRoot,
- readyForLiveAcceptance,
- readyForVideoAb,
- failures: strictFailures.length,
- warnings: warnings.length,
- summary: summaryPath
- }, null, 2));
- if (strict && strictFailures.length && !allowFailure) process.exitCode = 2;
- }
- function check(name, pass, note, severity = 'fail') {
- return {
- name,
- status: pass ? 'pass' : severity,
- note: pass ? '' : note
- };
- }
- function buildNextActions({ readyForLiveAcceptance, readyForVideoAb, warnings, strictFailures }) {
- const actions = [];
- if (!readyForLiveAcceptance) {
- actions.push('补齐 Parse sessionToken 和 company 后运行 npm run live:preflight -- --strict。');
- } else {
- actions.push('可运行 npm run live:acceptance 做小规模真实 live 检索。');
- }
- if (!readyForVideoAb) {
- actions.push('补齐视频分析 base URL、模型 doubao-seed-2-0-pro 和运行时 token 后,再运行 npm run acceptance:video-ab。');
- } else {
- actions.push('可运行 npm run acceptance:video-ab 验证视频证据是否提升提号结果。');
- }
- if (warnings.length) actions.push('provider 警告不是发布阻断,但不能据此宣称真实 provider 或视频链路已验证。');
- if (strictFailures.length) actions.push('严格模式下存在 fail 项,不能启动 overnight live 长跑。');
- return actions;
- }
- function safeMessage(error) {
- return redactSecrets(error && error.message ? error.message : String(error)).slice(0, 160);
- }
- function parseArgs(argv) {
- const args = {};
- for (let index = 0; index < argv.length; index += 1) {
- const raw = argv[index];
- if (!raw.startsWith('--')) continue;
- const key = raw.slice(2).replace(/-([a-z])/g, (_, char) => char.toUpperCase());
- const next = argv[index + 1];
- if (!next || next.startsWith('--')) args[key] = true;
- else {
- args[key] = next;
- index += 1;
- }
- }
- return args;
- }
- main().catch(error => {
- console.error(redactSecrets(error && error.stack ? error.stack : String(error)));
- process.exit(1);
- });
|