merge: server architecture modularization

This commit is contained in:
john
2026-07-24 19:18:14 +08:00
113 changed files with 29618 additions and 5864 deletions
@@ -0,0 +1,264 @@
import fs from 'node:fs';
import path from 'node:path';
import { createAgentRunGateway } from '../agent-run-gateway.mjs';
import { scanWorkspaceFilesForProhibitedBrowserStorage } from '../mindspace-browser-storage-policy.mjs';
import { evaluatePageDataHtmlContent } from '../mindspace-page-data-finish-guard.mjs';
import { normalizeWorkspaceRelativePath } from '../mindspace-pages.mjs';
import { resolveMindSpaceUserPublishDir } from '../mindspace-runtime-config.mjs';
import { policyAllowsAction } from '../page-access-policy.mjs';
import { detectPageDataDatasetUsageFromHtml } from '../page-data-html-detect.mjs';
import { readPageAccessPolicy } from '../page-data-policy-store.mjs';
import { createTkmindProxy } from '../tkmind-proxy.mjs';
import { createToolGateway } from '../tool-gateway.mjs';
function isEnabledFlag(value, fallback = '') {
return ['1', 'true', 'yes', 'on'].includes(
String(value ?? fallback).trim().toLowerCase(),
);
}
export function createPortalRunDeliverablesValidator({
h5Root,
resolveMindSpaceUserPublishDirFn =
resolveMindSpaceUserPublishDir,
normalizeWorkspaceRelativePathFn =
normalizeWorkspaceRelativePath,
evaluatePageDataHtmlContentFn =
evaluatePageDataHtmlContent,
readPageAccessPolicyFn = readPageAccessPolicy,
detectPageDataDatasetUsageFromHtmlFn =
detectPageDataDatasetUsageFromHtml,
policyAllowsActionFn = policyAllowsAction,
scanWorkspaceFilesForProhibitedBrowserStorageFn =
scanWorkspaceFilesForProhibitedBrowserStorage,
resolvePathFn = path.resolve,
pathSeparator = path.sep,
existsSyncFn = fs.existsSync,
readFileSyncFn = fs.readFileSync,
} = {}) {
if (!h5Root) {
throw new Error(
'createPortalRunDeliverablesValidator requires h5Root',
);
}
return async function validateRunDeliverables({
userId,
deliverables,
}) {
const publishDir =
resolveMindSpaceUserPublishDirFn(h5Root, {
id: userId,
});
const resolvedPublishDir =
resolvePathFn(publishDir);
const pageDataErrors = [];
for (const page of deliverables?.pages ?? []) {
const relativePath =
normalizeWorkspaceRelativePathFn(
page.workspaceRelativePath,
);
if (!relativePath?.startsWith('public/')) continue;
const filePath = resolvePathFn(
publishDir,
relativePath,
);
if (
!filePath.startsWith(
`${resolvedPublishDir}${pathSeparator}`,
) ||
!existsSyncFn(filePath)
) {
continue;
}
const html = readFileSyncFn(filePath, 'utf8');
const evaluation =
evaluatePageDataHtmlContentFn(html, {
relativePath,
});
if (!evaluation.usesPageDataApi) continue;
for (const issue of evaluation.issues) {
pageDataErrors.push({
code: issue,
message: `${relativePath} Page Data HTML 不可交付:${issue}`,
});
}
const policy = page.pageId
? readPageAccessPolicyFn(
publishDir,
page.pageId,
)
: null;
for (const [dataset, actions] of
detectPageDataDatasetUsageFromHtmlFn(html)) {
for (const action of ['read', 'insert']) {
if (
actions?.[action] &&
!policyAllowsActionFn(
policy,
dataset,
action,
)
) {
pageDataErrors.push({
code: 'page_data_policy_action_missing',
message: `${relativePath}${dataset}.${action} 未获最终 policy 授权或 dataset 已关闭`,
});
}
}
}
}
const violations =
scanWorkspaceFilesForProhibitedBrowserStorageFn({
publishDir,
relativePaths: (deliverables?.pages ?? [])
.map((page) => page.workspaceRelativePath)
.filter(Boolean),
});
return {
errors: [
...pageDataErrors,
...violations.map((violation) => ({
code: 'browser_storage_forbidden',
message: `${violation.relativePath} 使用 ${violation.apis.join(', ')}`,
})),
],
};
};
}
export function bootstrapPortalGatewayServices({
pool,
h5Root,
env = process.env,
apiTarget,
apiTargets,
apiSecret,
userAuth,
sessionAccess,
sessionStreamStore,
llmProviderService,
subscriptionService,
sessionSnapshotService,
conversationMemoryService,
memoryV2,
systemDisclosurePolicyService,
mindSpaceAssets,
directChatService,
chatIntentRouter,
syncUserGeneratedPages,
isSessionPageDeliveryActive,
createTkmindProxyFn = createTkmindProxy,
createToolGatewayFn = createToolGateway,
createAgentRunGatewayFn = createAgentRunGateway,
createRunDeliverablesValidatorFn =
createPortalRunDeliverablesValidator,
readAssetFileFn = fs.promises.readFile,
} = {}) {
if (
!pool ||
!h5Root ||
!userAuth ||
!sessionAccess ||
!llmProviderService ||
typeof syncUserGeneratedPages !== 'function' ||
typeof isSessionPageDeliveryActive !== 'function'
) {
throw new Error(
'bootstrapPortalGatewayServices requires gateway dependencies',
);
}
// GOOSED PROXY BOUNDARY: H5 chat → goosed unique entry.
const tkmindProxy = createTkmindProxyFn({
apiTarget,
apiTargets,
apiSecret,
userAuth,
sessionAccess,
sessionStreamStore,
llmProviderService,
subscriptionService,
sessionSnapshotService,
conversationMemoryService,
memoryV2,
systemDisclosurePolicyService,
localFetchAsset: mindSpaceAssets
? async (userId, assetId) => {
const { asset, path: assetPath } =
await mindSpaceAssets.readAsset(
userId,
assetId,
);
const buffer =
await readAssetFileFn(assetPath);
return {
buffer,
mimeType: asset.mimeType,
};
}
: null,
});
const toolGateway = createToolGatewayFn({
llmProviderService,
});
const validateRunDeliverables =
createRunDeliverablesValidatorFn({ h5Root });
const agentRunGateway = createAgentRunGatewayFn({
pool,
userAuth,
sessionAccess,
tkmindProxy,
toolGateway,
directChatService,
systemDisclosurePolicyService,
chatIntentRouter,
sessionSnapshotService,
conversationMemoryService,
observePersonalMemoryOnSuccess: async ({
userId,
sessionId,
userMessage,
}) => {
if (!memoryV2?.observePersonalMemory) return;
await memoryV2.observePersonalMemory({
userId,
sessionId,
messages: [userMessage],
});
},
syncUserPagesOnSuccess: async ({
userId,
sessionId,
runStartedAtMs,
}) =>
syncUserGeneratedPages(userId, {
sessionId,
sinceMs: runStartedAtMs,
}),
isSessionExternallyBusy: ({ sessionId }) =>
isSessionPageDeliveryActive(sessionId),
validateRunDeliverables,
autoDispatch: isEnabledFlag(
env.MEMIND_AGENT_RUN_AUTODISPATCH,
'1',
),
maxConcurrentRuns: Number(
env.MEMIND_AGENT_RUN_QUEUE_CONCURRENCY ?? 1,
),
runTimeoutMs: Number(
env.MEMIND_AGENT_RUN_TIMEOUT_MS ??
15 * 60 * 1000,
),
});
return {
tkmindProxy,
toolGateway,
agentRunGateway,
validateRunDeliverables,
};
}