import { resolveAnalyticsOwnerLabel, resolveAnalyticsOwnerSegment, resolveAnalyticsPlan, sendMindSpaceAnalyticsEvent, } from '../mindspace-analytics.mjs'; import { sendMindSpaceRybbitEvent, } from '../mindspace-rybbit.mjs'; import { createPageEditSessionService } from '../mindspace-page-edit-session.mjs'; import { createNotificationDispatcher } from '../notification-dispatcher.mjs'; import { startScheduleReminderWorker } from '../schedule-reminder-worker.mjs'; import { isPassiveCanaryRuntime } from './portal-runtime-role.mjs'; import { loadWechatMpModule } from '../wechat-mp-loader.mjs'; import { createToolGateway } from '../tool-gateway.mjs'; import { createPageDataDeliveryCodeReviewService, } from '../page-data-delivery-code-review.mjs'; export async function bootstrapPortalIntegrationServices({ pool, h5Root, codeRoot = h5Root, env = process.env, usersRoot, wechatMpConfig, mindSpacePublicFinish, userAuth, sessionAccess, tkmindProxy, scheduleService, wechatScheduleLlmConfigService, llmProviderService, chatIntentRouter, systemDisclosurePolicyService, sessionSnapshotService, mindSpaceAnalyticsConfig, mindSpaceRybbitConfig, subscriptionService, apiTarget, apiSecret, mindSpacePages, mindSpacePageLiveEdit, logger = console, loadWechatMpModuleFn = loadWechatMpModule, resolveAnalyticsOwnerSegmentFn = resolveAnalyticsOwnerSegment, resolveAnalyticsOwnerLabelFn = resolveAnalyticsOwnerLabel, resolveAnalyticsPlanFn = resolveAnalyticsPlan, sendMindSpaceAnalyticsEventFn = sendMindSpaceAnalyticsEvent, sendMindSpaceRybbitEventFn = sendMindSpaceRybbitEvent, createNotificationDispatcherFn = createNotificationDispatcher, startScheduleReminderWorkerFn = startScheduleReminderWorker, createPageEditSessionServiceFn = createPageEditSessionService, createToolGatewayFn = createToolGateway, createPageDataDeliveryCodeReviewServiceFn = createPageDataDeliveryCodeReviewService, setIntervalFn = setInterval, } = {}) { if ( !pool || !h5Root || !usersRoot || !userAuth || !sessionAccess || !tkmindProxy || !llmProviderService ) { throw new Error( 'bootstrapPortalIntegrationServices requires Portal integration dependencies', ); } const wechatMpRoot = codeRoot || h5Root; logger.log?.(`[WeChat MP] Loading runtime module from code root: ${wechatMpRoot}`); const wechatMp = await loadWechatMpModuleFn(wechatMpRoot); const pageDataDeliveryReviewer = wechatMpConfig?.pageDataAiderReviewEnabled === true ? createPageDataDeliveryCodeReviewServiceFn({ toolGateway: createToolGatewayFn({ llmProviderService, env, }), userAuth, timeoutMs: Number( env.H5_WECHAT_MP_PAGE_DATA_AIDER_REVIEW_TIMEOUT_MS ?? 15 * 60 * 1000, ), logger, }) : null; const wechatMpService = wechatMp.createWechatMpService({ config: wechatMpConfig, userAuth, sessionAccess, htmlDeliveryAuthority: typeof mindSpacePublicFinish ?.prepareWechatHtmlDelivery === 'function' && typeof mindSpacePublicFinish ?.ensureWechatFreshPageThumbnails === 'function' ? mindSpacePublicFinish : null, pageDataFinishGuard: typeof mindSpacePublicFinish ?.prepareWechatPageDataDelivery === 'function' ? mindSpacePublicFinish : null, pageDataDeliveryReviewer, apiFetch: tkmindProxy.apiFetch, startAgentSession: ({ userId, workingDir, sessionPolicy, }) => tkmindProxy.startSessionForUser(userId, { workingDir, sessionPolicy, origin: 'wechat', }), sessionApiFetch: async ( sessionId, pathname, init, ) => { const target = await tkmindProxy.resolveTarget(sessionId); return tkmindProxy.apiFetchTo( target, pathname, init, ); }, submitSessionReply: ({ userId, sessionId, requestId, userMessage, options, }) => tkmindProxy.submitSessionReplyForUser( userId, sessionId, requestId, userMessage, options, ), scheduleService: env.H5_SCHEDULE_ENABLED === '1' ? scheduleService : null, wechatScheduleLlmConfigService, llmProviderService, chatIntentRouter, systemDisclosurePolicyService, sessionIntentClassifier: ({ text }) => chatIntentRouter?.classifySessionAction({ text, }), onPageGenerated: async ({ userId, sessionId, artifacts = [], }) => { for (const artifact of artifacts) { const pageOwner = (await userAuth ?.getUserById(userId) .catch(() => null)) ?? {}; const analyticsPayload = { eventName: 'page_generated', ownerId: userId, ownerSegment: resolveAnalyticsOwnerSegmentFn( pageOwner, ), ownerLabel: resolveAnalyticsOwnerLabelFn( pageOwner, ), planType: resolveAnalyticsPlanFn(pageOwner), generatedAt: new Date().toISOString(), pageId: artifact.relativePath, publicationId: sessionId, agentRunId: sessionId, channel: 'wechat_mp', url: artifact.url || artifact.relativePath || '/', }; void sendMindSpaceAnalyticsEventFn({ config: mindSpaceAnalyticsConfig, ...analyticsPayload, }); void sendMindSpaceRybbitEventFn({ config: mindSpaceRybbitConfig, ...analyticsPayload, }); } }, applySessionLlmProvider: (sessionId) => tkmindProxy.applySessionLlmProvider(sessionId), refreshSessionSnapshot: sessionSnapshotService?.isEnabled() ? (sessionId, userId) => sessionSnapshotService.refresh( sessionId, userId, async (pathname, init) => { const target = await tkmindProxy.resolveTarget( sessionId, ); return tkmindProxy.apiFetchTo( target, pathname, init, ); }, ) : null, }); const notificationDispatcher = createNotificationDispatcherFn({ sendWechatTextToUser: wechatMpService?.enabled ? (userId, text) => wechatMpService.sendTextToUser( userId, text, ) : null, }); userAuth.setRechargeNotifier( async ({ userId, title, body, dedupeKey }) => { await notificationDispatcher.sendRechargeSuccess({ userId, title, body, dedupeKey, }); }, ); let scheduleReminderWorker = null; if ( !isPassiveCanaryRuntime(env) && env.H5_REMINDER_WORKER_ENABLED === '1' && wechatMpService?.enabled && scheduleService ) { scheduleReminderWorker = startScheduleReminderWorkerFn({ scheduleService, notificationDispatcher, }); logger.log('Schedule reminder worker enabled'); } let subscriptionExpiryTimer = null; if (subscriptionService && !isPassiveCanaryRuntime(env)) { subscriptionExpiryTimer = setIntervalFn( async () => { try { const { renewed, failed } = await subscriptionService.processAutoRenewals(); if (renewed > 0) { logger.log( `Auto-renewed ${renewed} subscription(s)`, ); } if (failed > 0) { logger.log( `Auto-renew failed for ${failed} subscription(s) (balance insufficient)`, ); } const expired = await subscriptionService.expireStaleSubscriptions(); if (expired > 0) { logger.log( `Expired ${expired} stale subscription(s)`, ); } } catch (error) { logger.warn( 'Subscription expiry check failed:', error, ); } }, 60 * 60 * 1000, ); subscriptionExpiryTimer.unref?.(); } const mindSpacePageEditSession = createPageEditSessionServiceFn({ apiTarget, apiSecret, userAuth, sessionAccess, pageService: mindSpacePages, pageLiveEdit: mindSpacePageLiveEdit, llmProviderService, }); if (wechatMpService?.enabled) { logger.log('WeChat MP webhook enabled'); } logger.log( `User auth enabled (MySQL), workspace root: ${usersRoot}`, ); return { wechatMpService, notificationDispatcher, scheduleReminderWorker, subscriptionExpiryTimer, mindSpacePageEditSession, }; }