1ca06e58e7
Memind CI / Test, build, and release guards (push) Successful in 8m42s
Wait for HTML materialization before releasing delivery contracts, pass verified MindSpace URLs through proactive WeChat sends, resend on reconcile when pages land late, and report deferred customer-service delivery as unsent. Co-authored-by: Cursor <cursoragent@cursor.com>
366 lines
10 KiB
JavaScript
366 lines
10 KiB
JavaScript
import {
|
|
resolveAnalyticsOwnerLabel,
|
|
resolveAnalyticsOwnerSegment,
|
|
resolveAnalyticsPlan,
|
|
sendMindSpaceAnalyticsEvent,
|
|
} from '../mindspace-analytics.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 { isScheduledTaskWorkerEnabled } from '../scheduled-task-worker-config.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,
|
|
wechatIntentRouter = null,
|
|
llmProviderService,
|
|
chatIntentRouter,
|
|
systemDisclosurePolicyService,
|
|
mindSpaceAnalyticsConfig,
|
|
subscriptionService,
|
|
apiTarget,
|
|
apiSecret,
|
|
mindSpacePages,
|
|
mindSpacePageLiveEdit,
|
|
logger = console,
|
|
loadWechatMpModuleFn = loadWechatMpModule,
|
|
resolveAnalyticsOwnerSegmentFn =
|
|
resolveAnalyticsOwnerSegment,
|
|
resolveAnalyticsOwnerLabelFn =
|
|
resolveAnalyticsOwnerLabel,
|
|
resolveAnalyticsPlanFn = resolveAnalyticsPlan,
|
|
sendMindSpaceAnalyticsEventFn =
|
|
sendMindSpaceAnalyticsEvent,
|
|
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,
|
|
h5Root,
|
|
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,
|
|
scheduledTaskService:
|
|
env.H5_SCHEDULE_ENABLED === '1'
|
|
? scheduledTaskService
|
|
: null,
|
|
wechatScheduleLlmConfigService,
|
|
llmProviderService,
|
|
chatIntentRouter,
|
|
wechatIntentRouter,
|
|
mysqlPool: pool,
|
|
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,
|
|
});
|
|
}
|
|
},
|
|
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, options) =>
|
|
wechatMpService.sendTextToUser(
|
|
userId,
|
|
text,
|
|
options,
|
|
)
|
|
: 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) &&
|
|
isScheduledTaskWorkerEnabled(env) &&
|
|
scheduledTaskService &&
|
|
userAuth &&
|
|
tkmindProxy
|
|
) {
|
|
scheduledTaskWorker =
|
|
startScheduledTaskWorkerFn({
|
|
scheduledTaskService,
|
|
scheduleService,
|
|
userAuth,
|
|
tkmindProxy,
|
|
sessionSnapshotService,
|
|
notificationDispatcher,
|
|
pool,
|
|
h5Root,
|
|
logger,
|
|
});
|
|
logger.log('Scheduled task worker enabled');
|
|
} else if (
|
|
!isPassiveCanaryRuntime(env)
|
|
&& scheduledTaskService
|
|
&& !isScheduledTaskWorkerEnabled(env)
|
|
) {
|
|
logger.warn?.(
|
|
'Scheduled task worker disabled: set H5_SCHEDULED_TASK_WORKER_ENABLED=1 or H5_REMINDER_WORKER_ENABLED=1',
|
|
);
|
|
}
|
|
|
|
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,
|
|
};
|
|
}
|