Files
memind/server/portal-integration-services-bootstrap.mjs
T
john 005612029f
Memind CI / Test, build, and release guards (push) Failing after 5s
Fix scheduled task page delivery unlock and notification links.
Finalize page delivery contracts after worker execution, fetch session messages via tkmindProxy, and append public page URLs to WeChat notifications.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 09:06:07 +08:00

358 lines
9.7 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 { createNotificationDispatcher } from '../notification-dispatcher.mjs';
import { startScheduleReminderWorker } from '../schedule-reminder-worker.mjs';
import { startScheduledTaskWorker } from '../scheduled-task-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,
scheduledTaskService = null,
sessionSnapshotService = null,
wechatScheduleLlmConfigService,
llmProviderService,
chatIntentRouter,
systemDisclosurePolicyService,
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,
startScheduledTaskWorkerFn =
startScheduledTaskWorker,
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 scheduledTaskWorker = null;
if (
!isPassiveCanaryRuntime(env) &&
env.H5_SCHEDULED_TASK_WORKER_ENABLED === '1' &&
scheduledTaskService &&
userAuth &&
tkmindProxy
) {
scheduledTaskWorker =
startScheduledTaskWorkerFn({
scheduledTaskService,
scheduleService,
userAuth,
tkmindProxy,
sessionSnapshotService,
notificationDispatcher,
pool,
h5Root,
logger,
});
logger.log('Scheduled task 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,
scheduledTaskWorker,
subscriptionExpiryTimer,
mindSpacePageEditSession,
};
}