fix: make goosed mcp paths container safe
This commit is contained in:
+14
-3
@@ -3,10 +3,18 @@ import { fileURLToPath } from 'node:url';
|
|||||||
import { resolveAgentGooseMode } from './policies.mjs';
|
import { resolveAgentGooseMode } from './policies.mjs';
|
||||||
|
|
||||||
/** Spawned as a separate Node process by goosed; must sit beside bundled portal runtime. */
|
/** Spawned as a separate Node process by goosed; must sit beside bundled portal runtime. */
|
||||||
export function resolveSandboxMcpServerPath() {
|
export function resolveSandboxMcpServerPath(overridePath) {
|
||||||
|
const normalized = String(overridePath ?? '').trim();
|
||||||
|
if (normalized) return normalized;
|
||||||
return path.join(path.dirname(fileURLToPath(import.meta.url)), 'mindspace-sandbox-mcp.mjs');
|
return path.join(path.dirname(fileURLToPath(import.meta.url)), 'mindspace-sandbox-mcp.mjs');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveSandboxMcpNodeExecPath(overridePath) {
|
||||||
|
const normalized = String(overridePath ?? '').trim();
|
||||||
|
if (normalized) return normalized;
|
||||||
|
return process.execPath;
|
||||||
|
}
|
||||||
|
|
||||||
export const CAPABILITY_CATALOG = [
|
export const CAPABILITY_CATALOG = [
|
||||||
{
|
{
|
||||||
key: 'shell',
|
key: 'shell',
|
||||||
@@ -307,7 +315,7 @@ export function buildAgentExtensionPolicy(
|
|||||||
'工作区沙箱文件系统与用户私有数据空间。用户私有数据空间是当前用户唯一的 SQLite 数据库,适合问卷、表单、清单、调研数据和分析中间表;不要用于账号、计费、权限、审计、公开平台数据或跨用户数据。',
|
'工作区沙箱文件系统与用户私有数据空间。用户私有数据空间是当前用户唯一的 SQLite 数据库,适合问卷、表单、清单、调研数据和分析中间表;不要用于账号、计费、权限、审计、公开平台数据或跨用户数据。',
|
||||||
display_name: 'sandbox-fs',
|
display_name: 'sandbox-fs',
|
||||||
bundled: false,
|
bundled: false,
|
||||||
cmd: sandboxMcp.nodeExecPath ?? process.execPath,
|
cmd: resolveSandboxMcpNodeExecPath(sandboxMcp.nodeExecPath),
|
||||||
// sandboxRoot passed as argv[2] so it works even if goosed doesn't forward envs
|
// sandboxRoot passed as argv[2] so it works even if goosed doesn't forward envs
|
||||||
args: [sandboxMcp.serverPath, sandboxMcp.sandboxRoot],
|
args: [sandboxMcp.serverPath, sandboxMcp.sandboxRoot],
|
||||||
// envs (goosed field name) as belt-and-suspenders backup
|
// envs (goosed field name) as belt-and-suspenders backup
|
||||||
@@ -350,7 +358,10 @@ export function buildAgentExtensionPolicy(
|
|||||||
extensions.push(makeExtension('platform', 'summon', summonTools));
|
extensions.push(makeExtension('platform', 'summon', summonTools));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (capabilities.code_sandbox) {
|
const enableCodeExecutionExtension = /^(1|true|yes)$/i.test(
|
||||||
|
process.env.TKMIND_ENABLE_CODE_EXECUTION_EXTENSION ?? '',
|
||||||
|
);
|
||||||
|
if (capabilities.code_sandbox && enableCodeExecutionExtension) {
|
||||||
extensions.push(makeExtension('platform', 'code_execution', []));
|
extensions.push(makeExtension('platform', 'code_execution', []));
|
||||||
}
|
}
|
||||||
if (capabilities.chat_recall) {
|
if (capabilities.chat_recall) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
clampUserCapabilities,
|
clampUserCapabilities,
|
||||||
DEFAULT_USER_CAPABILITIES,
|
DEFAULT_USER_CAPABILITIES,
|
||||||
normalizeCapabilityPatch,
|
normalizeCapabilityPatch,
|
||||||
|
resolveSandboxMcpNodeExecPath,
|
||||||
resolveSandboxMcpServerPath,
|
resolveSandboxMcpServerPath,
|
||||||
sandboxDeveloperTools,
|
sandboxDeveloperTools,
|
||||||
sandboxMcpTools,
|
sandboxMcpTools,
|
||||||
@@ -20,6 +21,18 @@ test('resolveSandboxMcpServerPath resolves to an existing MCP entry file', () =>
|
|||||||
assert.ok(fs.existsSync(serverPath));
|
assert.ok(fs.existsSync(serverPath));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('resolveSandboxMcpServerPath honors container-path override without host fs checks', () => {
|
||||||
|
assert.equal(
|
||||||
|
resolveSandboxMcpServerPath('/opt/portal/mindspace-sandbox-mcp.mjs'),
|
||||||
|
'/opt/portal/mindspace-sandbox-mcp.mjs',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resolveSandboxMcpNodeExecPath honors container-path override without host fs checks', () => {
|
||||||
|
assert.equal(resolveSandboxMcpNodeExecPath('/usr/local/bin/node'), '/usr/local/bin/node');
|
||||||
|
assert.equal(resolveSandboxMcpNodeExecPath(''), process.execPath);
|
||||||
|
});
|
||||||
|
|
||||||
test('default user policy blocks dangerous capabilities', () => {
|
test('default user policy blocks dangerous capabilities', () => {
|
||||||
assert.equal(DEFAULT_USER_CAPABILITIES.shell, false);
|
assert.equal(DEFAULT_USER_CAPABILITIES.shell, false);
|
||||||
assert.equal(DEFAULT_USER_CAPABILITIES.filesystem, false);
|
assert.equal(DEFAULT_USER_CAPABILITIES.filesystem, false);
|
||||||
@@ -222,6 +235,7 @@ test('static_publish with sandboxMcp uses stdio sandbox-fs extension instead of
|
|||||||
const sandboxExt = policy.extensionOverrides.find((ext) => ext.name === 'sandbox-fs');
|
const sandboxExt = policy.extensionOverrides.find((ext) => ext.name === 'sandbox-fs');
|
||||||
assert.ok(sandboxExt, 'sandbox-fs extension should be present');
|
assert.ok(sandboxExt, 'sandbox-fs extension should be present');
|
||||||
assert.equal(sandboxExt.type, 'stdio');
|
assert.equal(sandboxExt.type, 'stdio');
|
||||||
|
assert.equal(sandboxExt.cmd, process.execPath);
|
||||||
assert.equal(sandboxExt.envs.SANDBOX_ROOT, '/opt/h5/MindSpace/abc123');
|
assert.equal(sandboxExt.envs.SANDBOX_ROOT, '/opt/h5/MindSpace/abc123');
|
||||||
assert.equal(sandboxExt.args[1], '/opt/h5/MindSpace/abc123'); // also passed as argv[2]
|
assert.equal(sandboxExt.args[1], '/opt/h5/MindSpace/abc123'); // also passed as argv[2]
|
||||||
assert.ok(sandboxExt.available_tools.includes('write_file'));
|
assert.ok(sandboxExt.available_tools.includes('write_file'));
|
||||||
@@ -237,3 +251,17 @@ test('static_publish with sandboxMcp uses stdio sandbox-fs extension instead of
|
|||||||
assert.ok(!allTools.includes('write'), 'built-in write should not be exposed');
|
assert.ok(!allTools.includes('write'), 'built-in write should not be exposed');
|
||||||
assert.ok(!allTools.includes('shell'), 'shell should not be exposed');
|
assert.ok(!allTools.includes('shell'), 'shell should not be exposed');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('sandboxMcp honors container node executable override', () => {
|
||||||
|
const caps = { ...DEFAULT_USER_CAPABILITIES, static_publish: true };
|
||||||
|
const policy = buildAgentExtensionPolicy(caps, {
|
||||||
|
sandboxMcp: {
|
||||||
|
serverPath: '/opt/h5/mindspace-sandbox-mcp.mjs',
|
||||||
|
sandboxRoot: '/opt/h5/MindSpace/abc123',
|
||||||
|
nodeExecPath: '/usr/local/bin/node',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sandboxExt = policy.extensionOverrides.find((ext) => ext.name === 'sandbox-fs');
|
||||||
|
assert.equal(sandboxExt?.cmd, '/usr/local/bin/node');
|
||||||
|
});
|
||||||
|
|||||||
+23
-10
@@ -861,9 +861,12 @@ function validateCustomPayload(payload) {
|
|||||||
|
|
||||||
export function createLlmProviderService(
|
export function createLlmProviderService(
|
||||||
pool,
|
pool,
|
||||||
{ apiTarget, apiSecret, encryptionKey, apiFetchImpl = undiciFetch } = {},
|
{ apiTarget, apiTargets = [], apiSecret, encryptionKey, apiFetchImpl = undiciFetch } = {},
|
||||||
) {
|
) {
|
||||||
const launchStates = new Map();
|
const launchStates = new Map();
|
||||||
|
const syncTargets = [
|
||||||
|
...new Set([...(Array.isArray(apiTargets) ? apiTargets : []), apiTarget].filter(Boolean)),
|
||||||
|
];
|
||||||
|
|
||||||
function catalogItem(providerId) {
|
function catalogItem(providerId) {
|
||||||
return catalogById[providerId] ?? null;
|
return catalogById[providerId] ?? null;
|
||||||
@@ -1742,19 +1745,29 @@ export function createLlmProviderService(
|
|||||||
},
|
},
|
||||||
|
|
||||||
async syncSelectedToGoosed() {
|
async syncSelectedToGoosed() {
|
||||||
let resolved = await resolveExecutorProvider('goose');
|
const targets = syncTargets.length ? syncTargets : [apiTarget].filter(Boolean);
|
||||||
if (!resolved.ok) {
|
let lastResolved = null;
|
||||||
resolved = await resolveSelectedProvider();
|
for (const target of targets) {
|
||||||
}
|
const targetFetch = (url, init) => {
|
||||||
if (!resolved.ok) {
|
const pathname = `${url.pathname}${url.search}`;
|
||||||
return { ok: false, synced: false, message: resolved.message };
|
return goosedApiFetch(target, apiSecret, pathname, init, apiFetchImpl);
|
||||||
|
};
|
||||||
|
let resolved = await resolveExecutorProvider('goose', 'default', targetFetch);
|
||||||
|
if (!resolved.ok) {
|
||||||
|
resolved = await resolveSelectedProvider(targetFetch);
|
||||||
|
}
|
||||||
|
if (!resolved.ok) {
|
||||||
|
return { ok: false, synced: false, target, message: resolved.message };
|
||||||
|
}
|
||||||
|
lastResolved = resolved;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
synced: true,
|
synced: true,
|
||||||
source: resolved.source,
|
targets,
|
||||||
providerId: resolved.providerId,
|
source: lastResolved.source,
|
||||||
model: resolved.model,
|
providerId: lastResolved.providerId,
|
||||||
|
model: lastResolved.model,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
+66
-6
@@ -487,14 +487,15 @@ test('launchExecutor rejects openhands headless without instruction', async () =
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('launchExecutor returns friendly error when command is missing', async () => {
|
test('launchExecutor returns friendly error when command is missing', async () => {
|
||||||
|
const encryptedKey = encryptSecret('sk-x', 'unit-test-secret');
|
||||||
const keyRow = {
|
const keyRow = {
|
||||||
id: 'key-1',
|
id: 'key-1',
|
||||||
provider_id: 'custom_deepseek',
|
provider_id: 'custom_deepseek',
|
||||||
provider_kind: 'builtin',
|
provider_kind: 'builtin',
|
||||||
name: 'DeepSeek',
|
name: 'DeepSeek',
|
||||||
api_key_ciphertext: encryptSecret('sk-x', 'unit-test-secret').ciphertext,
|
api_key_ciphertext: encryptedKey.ciphertext,
|
||||||
api_key_iv: encryptSecret('sk-x', 'unit-test-secret').iv,
|
api_key_iv: encryptedKey.iv,
|
||||||
api_key_tag: encryptSecret('sk-x', 'unit-test-secret').tag,
|
api_key_tag: encryptedKey.tag,
|
||||||
default_model: 'deepseek-chat',
|
default_model: 'deepseek-chat',
|
||||||
status: 'active',
|
status: 'active',
|
||||||
is_selected: 1,
|
is_selected: 1,
|
||||||
@@ -513,13 +514,13 @@ test('launchExecutor returns friendly error when command is missing', async () =
|
|||||||
};
|
};
|
||||||
const pool = {
|
const pool = {
|
||||||
async query(sql) {
|
async query(sql) {
|
||||||
|
if (sql.includes('SELECT * FROM h5_llm_executor_bindings WHERE executor = ? AND purpose = ? LIMIT 1')) {
|
||||||
|
return [[bindingRow]];
|
||||||
|
}
|
||||||
if (sql.includes('SELECT * FROM h5_llm_executor_bindings')) return [[]];
|
if (sql.includes('SELECT * FROM h5_llm_executor_bindings')) return [[]];
|
||||||
if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) {
|
if (sql.includes('SELECT * FROM h5_llm_provider_keys WHERE id = ?')) {
|
||||||
return [[keyRow]];
|
return [[keyRow]];
|
||||||
}
|
}
|
||||||
if (sql.includes('SELECT * FROM h5_llm_executor_bindings WHERE executor = ? AND purpose = ? LIMIT 1')) {
|
|
||||||
return [[bindingRow]];
|
|
||||||
}
|
|
||||||
throw new Error(`Unexpected SQL: ${sql}`);
|
throw new Error(`Unexpected SQL: ${sql}`);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -597,6 +598,65 @@ test('syncProfileToGoosed writes provider, model and secret keys for builtin', a
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('syncSelectedToGoosed writes selected provider to every configured target', async () => {
|
||||||
|
const encrypted = encryptSecret('sk-test', 'unit-test-secret');
|
||||||
|
const row = {
|
||||||
|
id: 'key-deepseek',
|
||||||
|
provider_id: 'custom_deepseek',
|
||||||
|
provider_kind: 'builtin',
|
||||||
|
name: 'DeepSeek',
|
||||||
|
api_url: null,
|
||||||
|
base_path: null,
|
||||||
|
models_json: JSON.stringify(['deepseek-chat']),
|
||||||
|
goosed_provider_id: null,
|
||||||
|
engine: 'openai',
|
||||||
|
relay_provider: null,
|
||||||
|
api_key_ciphertext: encrypted.ciphertext,
|
||||||
|
api_key_iv: encrypted.iv,
|
||||||
|
api_key_tag: encrypted.tag,
|
||||||
|
default_model: 'deepseek-chat',
|
||||||
|
status: 'active',
|
||||||
|
is_selected: 1,
|
||||||
|
created_at: 1,
|
||||||
|
updated_at: 1,
|
||||||
|
};
|
||||||
|
const pool = {
|
||||||
|
async query(sql) {
|
||||||
|
if (sql.includes('SELECT * FROM h5_llm_executor_bindings')) return [[]];
|
||||||
|
if (sql.includes('is_selected = 1')) return [[row]];
|
||||||
|
throw new Error(`Unexpected SQL: ${sql}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const calls = [];
|
||||||
|
const mockFetch = async (url, init) => {
|
||||||
|
calls.push({ url: String(url), body: JSON.parse(init.body) });
|
||||||
|
if (String(url).includes('/chat/completions')) {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({ choices: [{ message: { content: 'ok' } }] }),
|
||||||
|
text: async () => JSON.stringify({ choices: [{ message: { content: 'ok' } }] }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { ok: true, text: async () => '' };
|
||||||
|
};
|
||||||
|
const service = createLlmProviderService(pool, {
|
||||||
|
apiTarget: 'https://127.0.0.1:18006',
|
||||||
|
apiTargets: ['https://127.0.0.1:18006', 'https://127.0.0.1:18007'],
|
||||||
|
apiSecret: 'secret',
|
||||||
|
encryptionKey: 'unit-test-secret',
|
||||||
|
apiFetchImpl: mockFetch,
|
||||||
|
});
|
||||||
|
const result = await service.syncSelectedToGoosed();
|
||||||
|
assert.equal(result.ok, true);
|
||||||
|
assert.deepEqual(result.targets, ['https://127.0.0.1:18006', 'https://127.0.0.1:18007']);
|
||||||
|
assert.deepEqual(
|
||||||
|
calls
|
||||||
|
.filter((call) => call.url.includes('/config/upsert') && call.body.key === 'GOOSE_PROVIDER')
|
||||||
|
.map((call) => new URL(call.url).port),
|
||||||
|
['18006', '18007'],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('syncProfileToGoosed tolerates missing /config/upsert endpoint', async () => {
|
test('syncProfileToGoosed tolerates missing /config/upsert endpoint', async () => {
|
||||||
const mockFetch = async (_url, init) => {
|
const mockFetch = async (_url, init) => {
|
||||||
if (String(_url).includes('/config/custom-providers')) {
|
if (String(_url).includes('/config/custom-providers')) {
|
||||||
|
|||||||
@@ -138,14 +138,15 @@ async function copyNodeModules() {
|
|||||||
if (!(await exists(nodeModulesDir))) {
|
if (!(await exists(nodeModulesDir))) {
|
||||||
throw new Error(`缺少 node_modules 目录: ${nodeModulesDir}`);
|
throw new Error(`缺少 node_modules 目录: ${nodeModulesDir}`);
|
||||||
}
|
}
|
||||||
|
const resolvedNodeModulesDir = await fs.realpath(nodeModulesDir);
|
||||||
console.log('==> 拷贝生产运行依赖 node_modules');
|
console.log('==> 拷贝生产运行依赖 node_modules');
|
||||||
await copyDir(nodeModulesDir, path.join(runtimeRoot, 'node_modules'));
|
await copyDir(resolvedNodeModulesDir, path.join(runtimeRoot, 'node_modules'));
|
||||||
await rewriteNodeModulesSymlinks(path.join(runtimeRoot, 'node_modules'));
|
await rewriteNodeModulesSymlinks(path.join(runtimeRoot, 'node_modules'), resolvedNodeModulesDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function rewriteNodeModulesSymlinks(runtimeNodeModulesDir) {
|
async function rewriteNodeModulesSymlinks(runtimeNodeModulesDir, sourceNodeModulesDir) {
|
||||||
console.log('==> 重写 node_modules 顶层符号链接为 runtime 内相对路径');
|
console.log('==> 重写 node_modules 顶层符号链接为 runtime 内相对路径');
|
||||||
const localNodeModulesRoot = `${nodeModulesDir}${path.sep}`;
|
const localNodeModulesRoot = `${sourceNodeModulesDir}${path.sep}`;
|
||||||
|
|
||||||
async function visit(dir) {
|
async function visit(dir) {
|
||||||
const entries = await fs.readdir(dir);
|
const entries = await fs.readdir(dir);
|
||||||
@@ -199,8 +200,11 @@ async function writeMetadata() {
|
|||||||
'export NODE_ENV=production',
|
'export NODE_ENV=production',
|
||||||
'export H5_PORT="${H5_PORT:-8081}"',
|
'export H5_PORT="${H5_PORT:-8081}"',
|
||||||
'export H5_PUBLIC_BASE_URL="${H5_PUBLIC_BASE_URL:-https://m.tkmind.cn}"',
|
'export H5_PUBLIC_BASE_URL="${H5_PUBLIC_BASE_URL:-https://m.tkmind.cn}"',
|
||||||
|
'export TKMIND_API_TARGETS="${TKMIND_API_TARGETS:-https://127.0.0.1:18006,https://127.0.0.1:18007,https://127.0.0.1:18008,https://127.0.0.1:18009}"',
|
||||||
'export TKMIND_API_TARGET="${TKMIND_API_TARGET:-https://127.0.0.1:18006}"',
|
'export TKMIND_API_TARGET="${TKMIND_API_TARGET:-https://127.0.0.1:18006}"',
|
||||||
'export TKMIND_API_TARGET_1="${TKMIND_API_TARGET_1:-https://127.0.0.1:18007}"',
|
'export TKMIND_API_TARGET_1="${TKMIND_API_TARGET_1:-https://127.0.0.1:18007}"',
|
||||||
|
'export GOOSED_MCP_NODE_PATH="${GOOSED_MCP_NODE_PATH:-/usr/local/bin/node}"',
|
||||||
|
'export GOOSED_MCP_SERVER_PATH="${GOOSED_MCP_SERVER_PATH:-/opt/portal/mindspace-sandbox-mcp.mjs}"',
|
||||||
'',
|
'',
|
||||||
'NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}"',
|
'NODE_BIN="${NODE_BIN:-/opt/homebrew/opt/node@24/bin/node}"',
|
||||||
'if [[ ! -x "${NODE_BIN}" ]]; then',
|
'if [[ ! -x "${NODE_BIN}" ]]; then',
|
||||||
@@ -232,7 +236,7 @@ async function writeMetadata() {
|
|||||||
'Key runtime differences must stay in .env, not in the artifact:',
|
'Key runtime differences must stay in .env, not in the artifact:',
|
||||||
' DATABASE_URL / MYSQL_*',
|
' DATABASE_URL / MYSQL_*',
|
||||||
' H5_PUBLIC_BASE_URL',
|
' H5_PUBLIC_BASE_URL',
|
||||||
' TKMIND_API_TARGET / TKMIND_API_TARGET_1',
|
' TKMIND_API_TARGETS / TKMIND_API_TARGET / TKMIND_API_TARGET_1',
|
||||||
' H5_USERS_ROOT / MINDSPACE_STORAGE_ROOT / MEMIND_SHARED_PUBLISH_ROOT',
|
' H5_USERS_ROOT / MINDSPACE_STORAGE_ROOT / MEMIND_SHARED_PUBLISH_ROOT',
|
||||||
'',
|
'',
|
||||||
'Deployment and operations transport:',
|
'Deployment and operations transport:',
|
||||||
|
|||||||
+15
-5
@@ -117,12 +117,21 @@ function loadEnvFile(filePath) {
|
|||||||
loadEnvFile(path.join(__dirname, '../../.env.local'));
|
loadEnvFile(path.join(__dirname, '../../.env.local'));
|
||||||
loadEnvFile(path.join(__dirname, '.env'));
|
loadEnvFile(path.join(__dirname, '.env'));
|
||||||
|
|
||||||
|
function parseApiTargets() {
|
||||||
|
const csvTargets = (process.env.TKMIND_API_TARGETS ?? '')
|
||||||
|
.split(',')
|
||||||
|
.map((value) => value.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const legacyTargets = [
|
||||||
|
process.env.TKMIND_API_TARGET ?? 'https://127.0.0.1:18006',
|
||||||
|
process.env.TKMIND_API_TARGET_1,
|
||||||
|
].filter(Boolean);
|
||||||
|
return [...new Set([...csvTargets, ...legacyTargets])];
|
||||||
|
}
|
||||||
|
|
||||||
const PORT = Number(process.env.H5_PORT ?? 8081);
|
const PORT = Number(process.env.H5_PORT ?? 8081);
|
||||||
const API_TARGET = process.env.TKMIND_API_TARGET ?? 'https://127.0.0.1:18006';
|
const API_TARGETS = parseApiTargets();
|
||||||
const API_TARGETS = [
|
const API_TARGET = API_TARGETS[0] ?? 'https://127.0.0.1:18006';
|
||||||
API_TARGET,
|
|
||||||
...(process.env.TKMIND_API_TARGET_1 ? [process.env.TKMIND_API_TARGET_1] : []),
|
|
||||||
];
|
|
||||||
const API_SECRET = process.env.TKMIND_SERVER__SECRET_KEY ?? 'local-dev-secret';
|
const API_SECRET = process.env.TKMIND_SERVER__SECRET_KEY ?? 'local-dev-secret';
|
||||||
const INTERNAL_AGENT_SECRET = process.env.MINDSPACE_INTERNAL_AGENT_SECRET ?? API_SECRET;
|
const INTERNAL_AGENT_SECRET = process.env.MINDSPACE_INTERNAL_AGENT_SECRET ?? API_SECRET;
|
||||||
const ACCESS_PASSWORD = process.env.H5_ACCESS_PASSWORD;
|
const ACCESS_PASSWORD = process.env.H5_ACCESS_PASSWORD;
|
||||||
@@ -486,6 +495,7 @@ async function bootstrapUserAuth() {
|
|||||||
await userAuth.ensureAdminUser();
|
await userAuth.ensureAdminUser();
|
||||||
llmProviderService = createLlmProviderService(pool, {
|
llmProviderService = createLlmProviderService(pool, {
|
||||||
apiTarget: API_TARGET,
|
apiTarget: API_TARGET,
|
||||||
|
apiTargets: API_TARGETS,
|
||||||
apiSecret: API_SECRET,
|
apiSecret: API_SECRET,
|
||||||
});
|
});
|
||||||
wordFilterService = createWordFilterService(pool);
|
wordFilterService = createWordFilterService(pool);
|
||||||
|
|||||||
+2
-1
@@ -1662,9 +1662,10 @@ export function createUserAuth(pool, options = {}) {
|
|||||||
try {
|
try {
|
||||||
const layout = await publishLayoutFor(user, { migrateLegacy: false });
|
const layout = await publishLayoutFor(user, { migrateLegacy: false });
|
||||||
sandboxMcp = {
|
sandboxMcp = {
|
||||||
serverPath: resolveSandboxMcpServerPath(),
|
serverPath: resolveSandboxMcpServerPath(process.env.GOOSED_MCP_SERVER_PATH),
|
||||||
sandboxRoot: layout.publishDir,
|
sandboxRoot: layout.publishDir,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
nodeExecPath: process.env.GOOSED_MCP_NODE_PATH,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[getAgentSessionPolicy] sandbox MCP setup failed, falling back:', err?.message);
|
console.warn('[getAgentSessionPolicy] sandbox MCP setup failed, falling back:', err?.message);
|
||||||
|
|||||||
Reference in New Issue
Block a user