/** * 单条新闻 → 公众号草稿流程的共享上下文。 * * 流程被拆成四个独立入口(生成 / 传图 / 生成配图 / 提交草稿),只有「生成」需要 * 完整的 Portal + Agent bootstrap;其余三个只要数据库连接和公众号凭证,因此这里 * 提供两级上下文,避免预览和推送也背上几十秒的 bootstrap 成本。 */ import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; import { createDbPool, isDatabaseConfigured } from '../db.mjs'; import { createMindSpaceWechatMpConfigService, fetchWechatMpAccessToken } from '../mindspace-wechat-mp-config.mjs'; import { resolveMindSpaceAnalyticsConfig } from '../mindspace-analytics.mjs'; import { resolveMindSpaceServerRuntimeOptions, resolvePortalH5Root, } from '../mindspace-runtime-config.mjs'; import { startWorkspaceAssetSyncWatcher } from '../mindspace-workspace-sync.mjs'; import { startWorkspaceThumbnailWatcher } from '../mindspace-workspace-thumbnails.mjs'; import { bootstrapPortalAgentServices } from '../server/portal-agent-services-bootstrap.mjs'; import { bootstrapPortalAuthServices } from '../server/portal-auth-services-bootstrap.mjs'; import { bootstrapPortalDomainServices } from '../server/portal-domain-services-bootstrap.mjs'; import { bootstrapPortalGatewayServices } from '../server/portal-gateway-services-bootstrap.mjs'; import { bootstrapPortalMemorySessionServices } from '../server/portal-memory-session-services-bootstrap.mjs'; import { resolveWechatFetch } from '../wechat-egress-fetch.mjs'; import { loadH5Environment } from './load-env.mjs'; import { loadMemindEnvFiles } from './memind-runtime-profile.mjs'; const scriptDir = path.dirname(fileURLToPath(import.meta.url)); export const CODE_ROOT = path.join(scriptDir, '..'); export function resolvePortalRoot(env = process.env) { return path.resolve(String(env.MEMIND_PORTAL_H5_ROOT ?? '/Users/john/Project/Memind').trim()); } /** 读取 stdin 上的 JSON 载荷。四个脚本统一用这种调用约定。 */ export function readStdinPayload() { const text = fs.readFileSync(0, 'utf8').trim(); if (!text) throw new Error('缺少 stdin JSON 输入'); return JSON.parse(text); } /** * 统一的环境准备:news-engine 用 MEMIND_DATABASE_URL,Memind 侧用 DATABASE_URL。 */ export function loadPushEnvironment(env = process.env) { loadH5Environment(scriptDir); loadMemindEnvFiles(resolvePortalRoot(env)); if (!String(env.DATABASE_URL ?? '').trim() && String(env.MEMIND_DATABASE_URL ?? '').trim()) { env.DATABASE_URL = String(env.MEMIND_DATABASE_URL).trim(); } env.MEMIND_PORTAL_H5_ROOT = resolvePortalRoot(env); env.MEMIND_WORKSPACE_MAINTENANCE = env.MEMIND_WORKSPACE_MAINTENANCE ?? '0'; env.MEMIND_PUSH_SKIP_SCHEMA_INIT = env.MEMIND_PUSH_SKIP_SCHEMA_INIT ?? '1'; if (!isDatabaseConfigured()) { throw new Error('DATABASE_URL / MEMIND_DATABASE_URL 未配置'); } return env; } /** * 轻量上下文:只要数据库和公众号凭证,用于传图 / 生成配图 / 提交草稿。 */ export function createLightContext(env = process.env) { const pool = createDbPool(); const h5Root = resolvePortalH5Root(CODE_ROOT, env); const wechatFetch = resolveWechatFetch(env); const wechatMpConfigService = createMindSpaceWechatMpConfigService(pool, { env, wechatFetch }); return { pool, h5Root, wechatFetch, wechatMpConfigService, async resolveWechat(userId) { const credentials = await wechatMpConfigService.getCredentials(userId); const token = await fetchWechatMpAccessToken(credentials, { wechatFetch }); return { credentials, accessToken: token.accessToken }; }, async close() { await pool.end().catch(() => {}); }, }; } function resolveInitSchemaFn(env = process.env) { const skip = String(env.MEMIND_PUSH_SKIP_SCHEMA_INIT ?? '1').trim().toLowerCase(); if (skip === '1' || skip === 'true' || skip === 'yes') return async () => {}; if (fs.existsSync(path.join(CODE_ROOT, 'schema.sql'))) return undefined; console.warn('[news-item] schema.sql 不在 memind-lib,跳过 initSchema(假定生产库已由 Portal 初始化)'); return async () => {}; } /** * 完整 Agent 上下文:只有页面生成阶段需要。 */ export async function bootstrapAgentContext(env = process.env) { const h5Root = resolvePortalH5Root(CODE_ROOT, env); const usersRoot = env.H5_USERS_ROOT?.trim() || path.join(h5Root, 'users'); const runtime = resolveMindSpaceServerRuntimeOptions(h5Root, env); const apiTarget = String(env.TKMIND_API_TARGET ?? 'https://127.0.0.1:18006').trim(); const apiSecret = env.TKMIND_SERVER__SECRET_KEY ?? 'local-dev-secret'; const analyticsConfig = resolveMindSpaceAnalyticsConfig(); const pool = createDbPool(); let userAuth = null; let sessionSnapshotService = null; const domainServices = await bootstrapPortalDomainServices({ pool, h5Root, env, runtime, analyticsConfig, getUserAuth: () => userAuth, getSessionSnapshotService: () => sessionSnapshotService, workspaceMaintenanceEnabled: env.MEMIND_WORKSPACE_MAINTENANCE !== '0', logger: console, initSchemaFn: resolveInitSchemaFn(env), }); const authServices = await bootstrapPortalAuthServices({ pool, h5Root, usersRoot, mindSearchConfigService: domainServices.mindSearchConfigService, env, logger: console, }); userAuth = authServices.userAuth; const agentServices = await bootstrapPortalAgentServices({ pool, h5Root, env, runtime, apiTarget, apiTargets: [apiTarget], apiSecret, userAuth, sessionAccess: authServices.sessionAccess, subscriptionService: authServices.subscriptionService, mindSpaceRuntimeAdapter: domainServices.mindSpaceRuntimeAdapter, mindSpaceAssets: domainServices.mindSpaceAssets, resolveUserIdByDirKey: domainServices.resolveUserIdByDirKey, workspaceMaintenanceEnabled: env.MEMIND_WORKSPACE_MAINTENANCE !== '0', startWorkspaceThumbnailWatcher, startWorkspaceAssetSyncWatcher, logger: console, }); const memorySessionServices = await bootstrapPortalMemorySessionServices({ pool, h5Root, env, llmProviderService: agentServices.llmProviderService, userAuth, sessionAccess: authServices.sessionAccess, logger: console, }); sessionSnapshotService = memorySessionServices.sessionSnapshotService; const gatewayServices = bootstrapPortalGatewayServices({ pool, h5Root, env, apiTarget, apiTargets: [apiTarget], apiSecret, userAuth, sessionAccess: authServices.sessionAccess, sessionStreamStore: memorySessionServices.sessionStreamStore, llmProviderService: agentServices.llmProviderService, subscriptionService: authServices.subscriptionService, billingConfigService: authServices.billingConfigService, sessionSnapshotService, conversationMemoryService: memorySessionServices.conversationMemoryService, memoryV2: memorySessionServices.memoryV2, systemDisclosurePolicyService: memorySessionServices.systemDisclosurePolicyService, wechatCursorExecutorPolicyService: memorySessionServices.wechatCursorExecutorPolicyService, mindSpaceAssets: domainServices.mindSpaceAssets, directChatService: memorySessionServices.directChatService, chatIntentRouter: memorySessionServices.chatIntentRouter, syncUserGeneratedPages: domainServices.mindSpacePageSync ? (userId, options) => domainServices.mindSpacePageSync.syncUserGeneratedPages(userId, options) : async () => {}, isSessionPageDeliveryActive: () => false, experienceService: agentServices.experienceService, }); return { pool, h5Root, userAuth, tkmindProxy: gatewayServices.tkmindProxy, agentRunGateway: gatewayServices.agentRunGateway, cursorExecutorPolicyService: memorySessionServices.wechatCursorExecutorPolicyService, sessionSnapshotService, wechatMpConfigService: createMindSpaceWechatMpConfigService(pool, { env }), async close() { await pool.end().catch(() => {}); }, }; } /** 四个脚本统一的 stdout 协议:最后一行是 JSON。 */ export function runScript(main) { main() .then((result) => { console.log(JSON.stringify({ ok: true, ...result })); }) .catch((error) => { console.log(JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error), code: error?.code ?? null, })); process.exit(1); }); }