feat: gate code tools by task mode
This commit is contained in:
@@ -38,3 +38,4 @@ Streaming runtime operations:
|
||||
node scripts/runtime-worker-drain.mjs status
|
||||
node scripts/runtime-worker-drain.mjs drain goosed-3
|
||||
node scripts/runtime-worker-drain.mjs undrain goosed-3
|
||||
node scripts/check-tool-runtime.mjs
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import mysql from 'mysql2/promise';
|
||||
|
||||
function loadEnvFile(filePath) {
|
||||
if (!fs.existsSync(filePath)) return;
|
||||
for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
const idx = trimmed.indexOf('=');
|
||||
if (idx < 0) continue;
|
||||
const key = trimmed.slice(0, idx).trim();
|
||||
const value = trimmed.slice(idx + 1).trim();
|
||||
if (!process.env[key]) process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
function parseMysqlConfig() {
|
||||
if (process.env.DATABASE_URL) {
|
||||
const url = new URL(process.env.DATABASE_URL);
|
||||
if (url.protocol !== 'mysql:') {
|
||||
throw new Error(`Unsupported DATABASE_URL scheme for tool runtime check: ${url.protocol}`);
|
||||
}
|
||||
return {
|
||||
host: url.hostname,
|
||||
port: Number(url.port || 3306),
|
||||
user: decodeURIComponent(url.username),
|
||||
password: decodeURIComponent(url.password),
|
||||
database: url.pathname.replace(/^\/+/, ''),
|
||||
charset: 'utf8mb4',
|
||||
};
|
||||
}
|
||||
return {
|
||||
host: process.env.MYSQL_HOST,
|
||||
port: Number(process.env.MYSQL_PORT || 3306),
|
||||
user: process.env.MYSQL_USER,
|
||||
password: process.env.MYSQL_PASSWORD,
|
||||
database: process.env.MYSQL_DATABASE,
|
||||
charset: 'utf8mb4',
|
||||
};
|
||||
}
|
||||
|
||||
loadEnvFile(process.env.MEMIND_ENV_FILE || path.join(process.cwd(), '.env'));
|
||||
|
||||
const conn = await mysql.createConnection(parseMysqlConfig());
|
||||
try {
|
||||
const [roleRows] = await conn.query(
|
||||
`SELECT capability_key, allowed
|
||||
FROM h5_capability_grants
|
||||
WHERE subject_type = 'role'
|
||||
AND subject_id = 'user'
|
||||
AND capability_key IN ('aider', 'openhands')
|
||||
ORDER BY capability_key`,
|
||||
);
|
||||
const roleDefaults = Object.fromEntries(
|
||||
roleRows.map((row) => [row.capability_key, Boolean(row.allowed)]),
|
||||
);
|
||||
|
||||
const [userRows] = await conn.query(
|
||||
`SELECT subject_id, capability_key, allowed
|
||||
FROM h5_capability_grants
|
||||
WHERE subject_type = 'user'
|
||||
AND capability_key IN ('aider', 'openhands')
|
||||
AND allowed = 1
|
||||
ORDER BY subject_id, capability_key`,
|
||||
);
|
||||
|
||||
const grantsByUser = new Map();
|
||||
for (const row of userRows) {
|
||||
if (!grantsByUser.has(row.subject_id)) grantsByUser.set(row.subject_id, []);
|
||||
grantsByUser.get(row.subject_id).push(row.capability_key);
|
||||
}
|
||||
const sampledUsers = [...grantsByUser.entries()].slice(0, 10).map(([userId, grantedTools]) => ({
|
||||
userId,
|
||||
grantedTools: grantedTools.sort(),
|
||||
chatHasCodeTools: false,
|
||||
codeHasCodeTools: grantedTools.includes('aider') || grantedTools.includes('openhands'),
|
||||
}));
|
||||
|
||||
const ok = Boolean(
|
||||
roleDefaults.aider === false &&
|
||||
roleDefaults.openhands === false &&
|
||||
sampledUsers.every((user) => user.chatHasCodeTools === false) &&
|
||||
sampledUsers.every((user) => user.codeHasCodeTools === true),
|
||||
);
|
||||
|
||||
console.log(JSON.stringify({
|
||||
ok,
|
||||
checkedAt: new Date().toISOString(),
|
||||
roleDefaults,
|
||||
whitelistUserCount: grantsByUser.size,
|
||||
sampledUsers,
|
||||
runtimePolicy: {
|
||||
defaultMode: 'chat',
|
||||
codeToolMode: 'code',
|
||||
chatInjectsCodeTools: false,
|
||||
aiderTimeoutMs: Number(process.env.MEMIND_AIDER_TIMEOUT_MS ?? 600_000),
|
||||
openhandsTimeoutMs: Number(process.env.MEMIND_OPENHANDS_TIMEOUT_MS ?? 900_000),
|
||||
},
|
||||
}, null, 2));
|
||||
process.exit(ok ? 0 : 1);
|
||||
} finally {
|
||||
await conn.end();
|
||||
}
|
||||
+31
-10
@@ -7370,7 +7370,7 @@ function sandboxMcpEnvs(sandboxMcp, mcpTools) {
|
||||
}
|
||||
return envs;
|
||||
}
|
||||
function buildAgentExtensionPolicy(capabilities, { unrestricted = false, policies = null, sandboxMcp = null } = {}) {
|
||||
function buildAgentExtensionPolicy(capabilities, { unrestricted = false, policies = null, sandboxMcp = null, toolMode = "chat" } = {}) {
|
||||
if (unrestricted) {
|
||||
return { extensionOverrides: null, enableContextMemory: true, gooseMode: "auto" };
|
||||
}
|
||||
@@ -7455,11 +7455,20 @@ function buildAgentExtensionPolicy(capabilities, { unrestricted = false, policie
|
||||
if (capabilities.charts) {
|
||||
extensions.push(makeExtension("builtin", "autovisualiser", []));
|
||||
}
|
||||
if (capabilities.aider) {
|
||||
extensions.push(makeExtension("platform", "aider", []));
|
||||
const codeToolMode = toolMode === "code" || toolMode === "code-task";
|
||||
if (codeToolMode && capabilities.aider) {
|
||||
extensions.push({
|
||||
...makeExtension("platform", "aider", []),
|
||||
timeout_ms: Number(process.env.MEMIND_AIDER_TIMEOUT_MS ?? 6e5),
|
||||
metadata: { runtime_scope: "code_tool_task" }
|
||||
});
|
||||
}
|
||||
if (capabilities.openhands) {
|
||||
extensions.push(makeExtension("platform", "openhands", []));
|
||||
if (codeToolMode && capabilities.openhands) {
|
||||
extensions.push({
|
||||
...makeExtension("platform", "openhands", []),
|
||||
timeout_ms: Number(process.env.MEMIND_OPENHANDS_TIMEOUT_MS ?? 9e5),
|
||||
metadata: { runtime_scope: "code_tool_task" }
|
||||
});
|
||||
}
|
||||
return {
|
||||
extensionOverrides: extensions,
|
||||
@@ -9250,6 +9259,13 @@ function createTkmindProxy({
|
||||
return {
|
||||
publicBaseUrl: process.env.H5_PUBLIC_BASE_URL ?? null,
|
||||
router: routerStatus,
|
||||
toolRuntime: {
|
||||
defaultMode: "chat",
|
||||
codeToolMode: "code",
|
||||
chatInjectsCodeTools: false,
|
||||
aiderTimeoutMs: Number(process.env.MEMIND_AIDER_TIMEOUT_MS ?? 6e5),
|
||||
openhandsTimeoutMs: Number(process.env.MEMIND_OPENHANDS_TIMEOUT_MS ?? 9e5)
|
||||
},
|
||||
targets: targetStatuses
|
||||
};
|
||||
}
|
||||
@@ -12287,7 +12303,7 @@ function createUserAuth(pool2, options = {}) {
|
||||
grantedSkills: grantedSkillNames(skillMap)
|
||||
};
|
||||
};
|
||||
const getAgentSessionPolicy = async (userId) => {
|
||||
const getAgentSessionPolicy = async (userId, { toolMode = "chat" } = {}) => {
|
||||
const user = await getUserById(userId);
|
||||
if (!user) throw new Error("\u7528\u6237\u4E0D\u5B58\u5728");
|
||||
const capabilityState = await resolveUserCapabilities(user);
|
||||
@@ -12295,9 +12311,10 @@ function createUserAuth(pool2, options = {}) {
|
||||
await syncUserSkillsForUser(user);
|
||||
if (capabilityState.unrestricted) {
|
||||
return {
|
||||
...buildAgentExtensionPolicy(capabilityState.capabilities, { unrestricted: true }),
|
||||
...buildAgentExtensionPolicy(capabilityState.capabilities, { unrestricted: true, toolMode }),
|
||||
policies: {},
|
||||
unrestricted: true
|
||||
unrestricted: true,
|
||||
toolMode
|
||||
};
|
||||
}
|
||||
const effectiveCapabilities = applyPoliciesToCapabilities(
|
||||
@@ -12327,13 +12344,16 @@ function createUserAuth(pool2, options = {}) {
|
||||
...buildAgentExtensionPolicy(effectiveCapabilities, {
|
||||
unrestricted: false,
|
||||
policies: policyState.policies,
|
||||
sandboxMcp
|
||||
sandboxMcp,
|
||||
toolMode
|
||||
}),
|
||||
capabilities: effectiveCapabilities,
|
||||
policies: policyState.policies,
|
||||
unrestricted: false
|
||||
unrestricted: false,
|
||||
toolMode
|
||||
};
|
||||
};
|
||||
const getCodeAgentSessionPolicy = async (userId) => getAgentSessionPolicy(userId, { toolMode: "code" });
|
||||
const getRoleCapabilities = async (role = "user") => {
|
||||
const roleDefaults = await listCapabilityGrants("role", role);
|
||||
const capabilities = {};
|
||||
@@ -13294,6 +13314,7 @@ function createUserAuth(pool2, options = {}) {
|
||||
seedRoleCapabilityDefaults,
|
||||
resolveUserCapabilities,
|
||||
getAgentSessionPolicy,
|
||||
getCodeAgentSessionPolicy,
|
||||
getRoleCapabilities,
|
||||
setRoleCapabilities,
|
||||
getUserCapabilities,
|
||||
|
||||
Reference in New Issue
Block a user