#!/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(); }