371900bae5
Memind CI / Test, build, and release guards (pull_request) Waiting to run
Centralize page HTML quota checks with grace-write semantics across page services and workspace tools, keep localhost MindSpace links clickable in chat display, and expand Page Data/static-page skill plus local dev docs for quota, delivery URLs, and native Aider/OpenHands tooling. Co-authored-by: Cursor <cursoragent@cursor.com>
120 lines
4.1 KiB
JavaScript
120 lines
4.1 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* 本机代码工具能力修复:role 默认 aider/openhands + 可选 canary 用户白名单。
|
|
* 用法: MEMIND_ENV_FILE=.env node scripts/fix-openhands-local-link.mjs
|
|
*
|
|
* MEMIND_ROLE_CODE_TOOLS_DEFAULT=1 → role=user 默认开启 aider/openhands(本地开发)
|
|
* 未设置或为 0 → role=user 默认关闭(生产基线)
|
|
*/
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { createDbPool } from '../db.mjs';
|
|
import { applyRoleSecurityBaseline } from '../security-baseline.mjs';
|
|
|
|
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
const CANARY_USER_ID = process.env.LOCAL_OPENHANDS_CANARY_USER_ID
|
|
?? '32035858-9a20-425b-89da-c118ef0779aa';
|
|
const CODE_TOOLS = ['aider', 'openhands'];
|
|
|
|
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 roleCodeToolsDefaultEnabled() {
|
|
return ['1', 'true', 'yes', 'on'].includes(
|
|
String(process.env.MEMIND_ROLE_CODE_TOOLS_DEFAULT ?? '').trim().toLowerCase(),
|
|
);
|
|
}
|
|
|
|
loadEnvFile(process.env.MEMIND_ENV_FILE || path.join(root, '.env'));
|
|
loadEnvFile(path.join(root, '.env.local'));
|
|
|
|
async function readRoleDefaults(pool) {
|
|
const [rows] = await pool.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`,
|
|
);
|
|
return Object.fromEntries(rows.map((row) => [row.capability_key, Boolean(row.allowed)]));
|
|
}
|
|
|
|
async function readUserGrants(pool, userId) {
|
|
const [rows] = await pool.query(
|
|
`SELECT capability_key, allowed
|
|
FROM h5_capability_grants
|
|
WHERE subject_type = 'user'
|
|
AND subject_id = ?
|
|
AND capability_key IN ('aider', 'openhands')
|
|
ORDER BY capability_key`,
|
|
[userId],
|
|
);
|
|
return Object.fromEntries(rows.map((row) => [row.capability_key, Boolean(row.allowed)]));
|
|
}
|
|
|
|
async function grantRoleTools(pool, role, tools, allowed) {
|
|
const now = Date.now();
|
|
for (const key of tools) {
|
|
await pool.query(
|
|
`INSERT INTO h5_capability_grants (subject_type, subject_id, capability_key, allowed, updated_at)
|
|
VALUES ('role', ?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE allowed = VALUES(allowed), updated_at = VALUES(updated_at)`,
|
|
[role, key, allowed ? 1 : 0, now],
|
|
);
|
|
}
|
|
}
|
|
|
|
async function grantUserTools(pool, userId, tools) {
|
|
const now = Date.now();
|
|
for (const key of tools) {
|
|
await pool.query(
|
|
`INSERT INTO h5_capability_grants (subject_type, subject_id, capability_key, allowed, updated_at)
|
|
VALUES ('user', ?, ?, 1, ?)
|
|
ON DUPLICATE KEY UPDATE allowed = VALUES(allowed), updated_at = VALUES(updated_at)`,
|
|
[userId, key, now],
|
|
);
|
|
}
|
|
}
|
|
|
|
const roleCodeToolsDefault = roleCodeToolsDefaultEnabled();
|
|
const pool = createDbPool();
|
|
try {
|
|
const beforeRole = await readRoleDefaults(pool);
|
|
const beforeUser = await readUserGrants(pool, CANARY_USER_ID);
|
|
|
|
if (roleCodeToolsDefault) {
|
|
await grantRoleTools(pool, 'user', CODE_TOOLS, true);
|
|
} else {
|
|
await applyRoleSecurityBaseline(pool, 'user');
|
|
await grantUserTools(pool, CANARY_USER_ID, CODE_TOOLS);
|
|
}
|
|
|
|
const afterRole = await readRoleDefaults(pool);
|
|
const afterUser = await readUserGrants(pool, CANARY_USER_ID);
|
|
const expectedRole = roleCodeToolsDefault;
|
|
|
|
console.log(JSON.stringify({
|
|
ok: afterRole.aider === expectedRole
|
|
&& afterRole.openhands === expectedRole
|
|
&& (!roleCodeToolsDefault || CODE_TOOLS.every((key) => afterRole[key] === true)),
|
|
roleCodeToolsDefault,
|
|
canaryUserId: CANARY_USER_ID,
|
|
before: { role: beforeRole, user: beforeUser },
|
|
after: { role: afterRole, user: afterUser },
|
|
}, null, 2));
|
|
} finally {
|
|
await pool.end();
|
|
}
|