feat: add memindadm runtime policy for agent code runs (Phase 1.5)

Persist code-run gates in admin DB and expose them via /auth/status so H5 can honor runtime policy without VITE rebuilds.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-23 21:33:27 +08:00
parent 13f5c740a1
commit 239c41f935
12 changed files with 639 additions and 43 deletions
+42 -23
View File
@@ -56,12 +56,32 @@ export function createPostAgentRunsHandler({
sessionAccess = null,
agentRunGateway,
mindSpaceAssetAgent = null,
codeRunPolicyService = null,
codeRunsEnabled = envFlag(process.env.MEMIND_AGENT_CODE_RUNS_ENABLED),
codeRunUserIds = parseUserIdSet(process.env.MEMIND_AGENT_CODE_RUNS_USER_IDS),
codeRunTaskTypes = parseTaskTypeSet(process.env.MEMIND_AGENT_CODE_RUN_TASK_TYPES),
requireCodeRunValidation = envFlag(process.env.MEMIND_AGENT_CODE_RUNS_REQUIRE_VALIDATION),
}) {
const sessionStore = sessionAccess ?? createSessionAccess({ userAuth, enabled: false });
async function resolveCodeRunPolicy(userId) {
if (codeRunPolicyService?.getEffectivePolicy) {
return codeRunPolicyService.getEffectivePolicy(userId);
}
return {
source: 'env',
enabled: codeRunsEnabled,
clientEnabled: codeRunsEnabled,
generalAutodetect: false,
pageDataDevAutodetect: false,
requireValidation: requireCodeRunValidation,
userAllowlist: [...codeRunUserIds],
taskTypeAllowlist: [...codeRunTaskTypes],
userAllowed:
!codeRunUserIds.size || codeRunUserIds.has(String(userId ?? '').trim()),
};
}
return async function postAgentRuns(request, response) {
try {
const sessionId = String(request.body?.session_id ?? '').trim() || null;
@@ -87,29 +107,28 @@ export function createPostAgentRunsHandler({
});
return;
}
if (toolMode === 'code' && !codeRunsEnabled) {
response.status(403).json({ message: '代码任务灰度未开启' });
return;
}
if (
toolMode === 'code' &&
codeRunUserIds.size > 0 &&
!codeRunUserIds.has(request.currentUser.id)
) {
response.status(403).json({ message: '当前用户未开启代码任务灰度' });
return;
}
if (
toolMode === 'code' &&
codeRunTaskTypes.size > 0 &&
(!taskType || !codeRunTaskTypes.has(taskType.toLowerCase()))
) {
response.status(403).json({ message: '当前代码任务类型未开启灰度' });
return;
}
if (toolMode === 'code' && requireCodeRunValidation && !hasExpectedFileValidation(userMessage)) {
response.status(400).json({ message: '代码任务必须声明产物校验规则' });
return;
if (toolMode === 'code') {
const codeRunPolicy = await resolveCodeRunPolicy(request.currentUser.id);
if (!codeRunPolicy.enabled) {
response.status(403).json({ message: '代码任务灰度未开启' });
return;
}
if (!codeRunPolicy.userAllowed) {
response.status(403).json({ message: '当前用户未开启代码任务灰度' });
return;
}
const taskTypeAllowlist = codeRunPolicy.taskTypeAllowlist ?? [];
if (
taskTypeAllowlist.length > 0 &&
(!taskType || !taskTypeAllowlist.map((item) => String(item).toLowerCase()).includes(taskType.toLowerCase()))
) {
response.status(403).json({ message: '当前代码任务类型未开启灰度' });
return;
}
if (codeRunPolicy.requireValidation && !hasExpectedFileValidation(userMessage)) {
response.status(400).json({ message: '代码任务必须声明产物校验规则' });
return;
}
}
if (sessionId) {
const owns = await sessionStore.validateOwnership(request.currentUser.id, sessionId);