Files
memind/server/portal-integration-services-bootstrap.mjs
T
2026-07-25 09:46:10 +08:00

300 lines
7.9 KiB
JavaScript

import {
resolveAnalyticsOwnerLabel,
resolveAnalyticsOwnerSegment,
resolveAnalyticsPlan,
sendMindSpaceAnalyticsEvent,
} from '../mindspace-analytics.mjs';
import {
sendMindSpaceRybbitEvent,
} from '../mindspace-rybbit.mjs';
import { createPageEditSessionService } from '../mindspace-page-edit-session.mjs';
import { resolveMindSpaceRuntimeConfig } from '../mindspace-runtime-config.mjs';
import { createNotificationDispatcher } from '../notification-dispatcher.mjs';
import { startScheduleReminderWorker } from '../schedule-reminder-worker.mjs';
import { loadWechatMpModule } from '../wechat-mp-loader.mjs';
export async function bootstrapPortalIntegrationServices({
pool,
h5Root,
env = process.env,
usersRoot,
wechatMpConfig,
authPool,
userAuth,
sessionAccess,
tkmindProxy,
scheduleService,
wechatScheduleLlmConfigService,
llmProviderService,
chatIntentRouter,
systemDisclosurePolicyService,
sessionSnapshotService,
mindSpaceAnalyticsConfig,
mindSpaceRybbitConfig,
subscriptionService,
apiTarget,
apiSecret,
mindSpacePages,
mindSpacePageLiveEdit,
logger = console,
loadWechatMpModuleFn = loadWechatMpModule,
resolveMindSpaceRuntimeConfigFn =
resolveMindSpaceRuntimeConfig,
resolveAnalyticsOwnerSegmentFn =
resolveAnalyticsOwnerSegment,
resolveAnalyticsOwnerLabelFn =
resolveAnalyticsOwnerLabel,
resolveAnalyticsPlanFn = resolveAnalyticsPlan,
sendMindSpaceAnalyticsEventFn =
sendMindSpaceAnalyticsEvent,
sendMindSpaceRybbitEventFn =
sendMindSpaceRybbitEvent,
createNotificationDispatcherFn =
createNotificationDispatcher,
startScheduleReminderWorkerFn =
startScheduleReminderWorker,
createPageEditSessionServiceFn =
createPageEditSessionService,
setIntervalFn = setInterval,
} = {}) {
if (
!pool ||
!h5Root ||
!usersRoot ||
!userAuth ||
!sessionAccess ||
!tkmindProxy ||
!llmProviderService
) {
throw new Error(
'bootstrapPortalIntegrationServices requires Portal integration dependencies',
);
}
const wechatMp = await loadWechatMpModuleFn(h5Root);
const wechatMpService =
wechatMp.createWechatMpService({
config: wechatMpConfig,
userAuth,
sessionAccess,
pageDataFinishGuard: authPool
? {
pool: authPool,
h5Root,
storageRoot:
resolveMindSpaceRuntimeConfigFn(
h5Root,
env,
).storageRoot,
}
: null,
apiFetch: tkmindProxy.apiFetch,
startAgentSession: ({
userId,
workingDir,
sessionPolicy,
}) =>
tkmindProxy.startSessionForUser(userId, {
workingDir,
sessionPolicy,
}),
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 (
env.H5_REMINDER_WORKER_ENABLED === '1' &&
wechatMpService?.enabled &&
scheduleService
) {
scheduleReminderWorker =
startScheduleReminderWorkerFn({
scheduleService,
notificationDispatcher,
});
logger.log('Schedule reminder worker enabled');
}
let subscriptionExpiryTimer = null;
if (subscriptionService) {
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,
};
}