feat: gate code agent runs server side

This commit is contained in:
John
2026-07-02 07:25:16 +08:00
parent 304a9eaea2
commit e989526886
7 changed files with 73 additions and 2 deletions
+2
View File
@@ -20,7 +20,9 @@ H5_PUBLIC_BASE_URL=http://127.0.0.1:5173
# MEMIND_RUNTIME_REDIS_NAMESPACE=memind:runtime
# Code-tool agent run 灰度开关(默认关闭)。
# 后端必须先开启 MEMIND_AGENT_CODE_RUNS_ENABLEDH5 构建开关才会生效。
# 开启后,页面编辑子聊天会传 tool_mode=code;普通聊天仍需同时开启 AUTODETECT 才会按文本自动识别代码任务。
# MEMIND_AGENT_CODE_RUNS_ENABLED=0
# VITE_AGENT_CODE_RUNS_ENABLED=0
# VITE_AGENT_CODE_RUNS_AUTODETECT=0
@@ -140,6 +140,8 @@ const toolGrants = await queryToolGrants().catch((err) => ({
}));
const buildFlags = {
serverCodeRunsEnabledEnv: process.env.MEMIND_AGENT_CODE_RUNS_ENABLED ?? null,
serverCodeRunsEnabledTruthy: truthy(process.env.MEMIND_AGENT_CODE_RUNS_ENABLED),
enabledEnv: process.env.VITE_AGENT_CODE_RUNS_ENABLED ?? null,
autodetectEnv: process.env.VITE_AGENT_CODE_RUNS_AUTODETECT ?? null,
enabledTruthy: truthy(process.env.VITE_AGENT_CODE_RUNS_ENABLED),
@@ -157,6 +159,7 @@ const ok = Boolean(
runtimePolicy.defaultMode === 'chat' &&
runtimePolicy.codeToolMode === 'code' &&
runtimePolicy.chatInjectsCodeTools === false &&
runtimePolicy.codeRunsEnabled === buildFlags.serverCodeRunsEnabledTruthy &&
roleDefaults.aider === false &&
roleDefaults.openhands === false &&
(toolGrants.sampledUsers ?? []).every((user) => user.chatHasCodeTools === false) &&
+15 -1
View File
@@ -6744,7 +6744,14 @@ function createAgentRunGateway({
}
// agent-run-routes.mjs
function createPostAgentRunsHandler({ userAuth: userAuth2, agentRunGateway: agentRunGateway2 }) {
function envFlag(value) {
return ["1", "true", "yes", "on"].includes(String(value ?? "").trim().toLowerCase());
}
function createPostAgentRunsHandler({
userAuth: userAuth2,
agentRunGateway: agentRunGateway2,
codeRunsEnabled = envFlag(process.env.MEMIND_AGENT_CODE_RUNS_ENABLED)
}) {
return async function postAgentRuns(request, response) {
try {
const sessionId = String(request.body?.session_id ?? "").trim() || null;
@@ -6769,6 +6776,10 @@ function createPostAgentRunsHandler({ userAuth: userAuth2, agentRunGateway: agen
});
return;
}
if (toolMode === "code" && !codeRunsEnabled) {
response.status(403).json({ message: "\u4EE3\u7801\u4EFB\u52A1\u7070\u5EA6\u672A\u5F00\u542F" });
return;
}
if (sessionId) {
const owns = await userAuth2.ownsSession(request.currentUser.id, sessionId);
if (!owns) {
@@ -9338,6 +9349,9 @@ function createTkmindProxy({
defaultMode: "chat",
codeToolMode: "code",
chatInjectsCodeTools: false,
codeRunsEnabled: ["1", "true", "yes", "on"].includes(
String(process.env.MEMIND_AGENT_CODE_RUNS_ENABLED ?? "").trim().toLowerCase()
),
aiderTimeoutMs: Number(process.env.MEMIND_AIDER_TIMEOUT_MS ?? 6e5),
openhandsTimeoutMs: Number(process.env.MEMIND_OPENHANDS_TIMEOUT_MS ?? 9e5)
},
+13 -1
View File
@@ -1,6 +1,14 @@
import { normalizeAgentRunToolMode } from './agent-run-gateway.mjs';
export function createPostAgentRunsHandler({ userAuth, agentRunGateway }) {
function envFlag(value) {
return ['1', 'true', 'yes', 'on'].includes(String(value ?? '').trim().toLowerCase());
}
export function createPostAgentRunsHandler({
userAuth,
agentRunGateway,
codeRunsEnabled = envFlag(process.env.MEMIND_AGENT_CODE_RUNS_ENABLED),
}) {
return async function postAgentRuns(request, response) {
try {
const sessionId = String(request.body?.session_id ?? '').trim() || null;
@@ -25,6 +33,10 @@ export function createPostAgentRunsHandler({ userAuth, agentRunGateway }) {
});
return;
}
if (toolMode === 'code' && !codeRunsEnabled) {
response.status(403).json({ message: '代码任务灰度未开启' });
return;
}
if (sessionId) {
const owns = await userAuth.ownsSession(request.currentUser.id, sessionId);
if (!owns) {
+34
View File
@@ -114,6 +114,7 @@ test('POST /agent/runs accepts explicit code tool mode and task type', async ()
return { id: 'run-code', status: 'queued' };
},
},
codeRunsEnabled: true,
});
const res = createResponseRecorder();
@@ -141,6 +142,39 @@ test('POST /agent/runs accepts explicit code tool mode and task type', async ()
});
});
test('POST /agent/runs rejects code tool mode when server gate is disabled', async () => {
const handler = createPostAgentRunsHandler({
userAuth: {
async ownsSession() {
throw new Error('should not check ownership after disabled code mode');
},
},
agentRunGateway: {
async createRun() {
throw new Error('should not be called');
},
},
codeRunsEnabled: false,
});
const res = createResponseRecorder();
await handler(
{
currentUser: { id: 'user-1' },
body: {
session_id: 'session-1',
request_id: 'req-code',
user_message: { role: 'user', content: [] },
tool_mode: 'code',
},
},
res,
);
assert.equal(res.statusCode, 403);
assert.deepEqual(res.body, { message: '代码任务灰度未开启' });
});
test('POST /agent/runs rejects unsupported tool mode', async () => {
const handler = createPostAgentRunsHandler({
userAuth: {
+3
View File
@@ -140,6 +140,8 @@ const toolGrants = await queryToolGrants().catch((err) => ({
}));
const buildFlags = {
serverCodeRunsEnabledEnv: process.env.MEMIND_AGENT_CODE_RUNS_ENABLED ?? null,
serverCodeRunsEnabledTruthy: truthy(process.env.MEMIND_AGENT_CODE_RUNS_ENABLED),
enabledEnv: process.env.VITE_AGENT_CODE_RUNS_ENABLED ?? null,
autodetectEnv: process.env.VITE_AGENT_CODE_RUNS_AUTODETECT ?? null,
enabledTruthy: truthy(process.env.VITE_AGENT_CODE_RUNS_ENABLED),
@@ -157,6 +159,7 @@ const ok = Boolean(
runtimePolicy.defaultMode === 'chat' &&
runtimePolicy.codeToolMode === 'code' &&
runtimePolicy.chatInjectsCodeTools === false &&
runtimePolicy.codeRunsEnabled === buildFlags.serverCodeRunsEnabledTruthy &&
roleDefaults.aider === false &&
roleDefaults.openhands === false &&
(toolGrants.sampledUsers ?? []).every((user) => user.chatHasCodeTools === false) &&
+3
View File
@@ -912,6 +912,9 @@ export function createTkmindProxy({
defaultMode: 'chat',
codeToolMode: 'code',
chatInjectsCodeTools: false,
codeRunsEnabled: ['1', 'true', 'yes', 'on'].includes(
String(process.env.MEMIND_AGENT_CODE_RUNS_ENABLED ?? '').trim().toLowerCase(),
),
aiderTimeoutMs: Number(process.env.MEMIND_AIDER_TIMEOUT_MS ?? 600_000),
openhandsTimeoutMs: Number(process.env.MEMIND_OPENHANDS_TIMEOUT_MS ?? 900_000),
},