mindspace: close authority boundaries

This commit is contained in:
john
2026-07-27 15:34:35 +08:00
parent dfab78c75a
commit e94052ff24
78 changed files with 11962 additions and 2162 deletions
+283 -5
View File
@@ -10,6 +10,7 @@
import path from 'node:path';
import fs from 'node:fs';
import os from 'node:os';
import readline from 'node:readline';
import { fileURLToPath } from 'node:url';
import { execFileSync } from 'node:child_process';
@@ -46,6 +47,36 @@ const PRIVATE_DATA_MAX_ROWS = Number(process.env.PRIVATE_DATA_MAX_ROWS ?? 200);
const PRIVATE_DATA_USER_ID = process.env.PRIVATE_DATA_USER_ID?.trim();
const AGENT_API_BASE_URL = process.env.MINDSPACE_AGENT_API_BASE_URL?.trim();
const INTERNAL_AGENT_SECRET = process.env.MINDSPACE_INTERNAL_AGENT_SECRET?.trim();
const MINDSPACE_MCP_BASE_URL =
process.env.MINDSPACE_MCP_BASE_URL
?.trim()
.replace(/\/+$/, '');
const MINDSPACE_MCP_SCOPED_TOKEN =
process.env.MINDSPACE_MCP_SCOPED_TOKEN
?.trim();
const MINDSPACE_WORKSPACE_REF =
process.env.MINDSPACE_WORKSPACE_REF
?.trim();
const MINDSPACE_SESSION_ID =
process.env.MINDSPACE_SESSION_ID?.trim();
const MINDSPACE_PACKAGE_ID =
process.env.MINDSPACE_PACKAGE_ID?.trim();
const LOGICAL_WORKSPACE_TOOLS = new Set([
'create_dir',
'edit_file',
'generate_long_image',
'list_dir',
'publish_page',
'read_file',
'write_file',
]);
const LOGICAL_WORKSPACE_ENABLED = Boolean(
MINDSPACE_MCP_BASE_URL &&
MINDSPACE_MCP_SCOPED_TOKEN &&
MINDSPACE_WORKSPACE_REF &&
MINDSPACE_SESSION_ID &&
MINDSPACE_PACKAGE_ID,
);
const allowedToolsEnv = process.env.ALLOWED_TOOLS?.trim();
const ALLOWED_TOOLS = allowedToolsEnv ? new Set(allowedToolsEnv.split(',').map((s) => s.trim())) : null;
@@ -99,16 +130,29 @@ function normalizeDocxSections(sections) {
});
}
function runGenerateDocxScript({ outputPath, title, sections }) {
function runGenerateDocxScript({
outputPath,
physicalOutputPath = null,
title,
sections,
}) {
const script = resolveDocxGenerateScript();
const python = process.env.PYTHON_BIN?.trim() || 'python3';
const targetOutputPath =
physicalOutputPath ?? outputPath;
const payload = JSON.stringify({
title: String(title ?? ''),
sections: normalizeDocxSections(sections),
});
const stdout = execFileSync(
python,
[script, '--json', '-', '--output', outputPath],
[
script,
'--json',
'-',
'--output',
targetOutputPath,
],
{
cwd: SANDBOX,
input: payload,
@@ -116,7 +160,9 @@ function runGenerateDocxScript({ outputPath, title, sections }) {
maxBuffer: 10 * 1024 * 1024,
},
);
const abs = resolveSandboxed(outputPath);
const abs = physicalOutputPath
? path.resolve(physicalOutputPath)
: resolveSandboxed(outputPath);
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) {
throw new Error(`generate_docx: 脚本执行后未找到 ${outputPath}`);
}
@@ -124,7 +170,11 @@ function runGenerateDocxScript({ outputPath, title, sections }) {
if (size < 64) {
throw new Error(`generate_docx: ${outputPath} 体积异常(${size} 字节)`);
}
return { bytes: size, stdout: String(stdout ?? '').trim() };
return {
body: fs.readFileSync(abs),
bytes: size,
stdout: String(stdout ?? '').trim(),
};
}
async function generateMindSpaceImage({ purpose, prompt, negativePrompt, idempotencyKey }) {
@@ -221,6 +271,22 @@ const ALL_TOOLS = [
required: ['path'],
},
},
{
name: 'publish_page',
description:
'通过 MindSpace Service 登记并交付已写入的 public/*.html 页面,返回 canonical workspace URL。仅在 scoped workspace RPC 启用时可用。',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description:
'页面工作区相对路径,例如 public/report.html',
},
},
required: ['path'],
},
},
{
name: 'generate_long_image',
description:
@@ -551,10 +617,181 @@ function resolveH5RootFromSandbox() {
return SANDBOX_MODULE_DIR;
}
async function callLogicalWorkspaceTool(
name,
args,
) {
const endpoint = new URL(
`mindspace/v1/mcp/${encodeURIComponent(
name,
)}`,
`${MINDSPACE_MCP_BASE_URL}/`,
);
const response = await fetch(endpoint, {
method: 'POST',
headers: {
accept: 'application/json',
authorization:
`Bearer ${MINDSPACE_MCP_SCOPED_TOKEN}`,
'content-type': 'application/json',
},
body: JSON.stringify({
arguments: args ?? {},
}),
signal: AbortSignal.timeout(
Number(
process.env
.MINDSPACE_MCP_REQUEST_TIMEOUT_MS ??
(name === 'generate_long_image'
? 90_000
: 30_000),
),
),
});
let payload = null;
try {
payload = await response.json();
} catch {
// The bounded error below intentionally avoids echoing an upstream body.
}
if (!response.ok || !payload?.ok) {
const error = new Error(
`MindSpace workspace tool ${name} failed (${response.status}): ${
String(
payload?.message ??
payload?.code ??
'unknown_error',
).slice(0, 240)
}`,
);
error.code =
payload?.code ??
'mindspace_workspace_tool_failed';
throw error;
}
const result = payload.result ?? {};
switch (name) {
case 'read_file':
return [
{
type: 'text',
text: String(result.content ?? ''),
},
];
case 'list_dir': {
const lines = (
Array.isArray(result.entries)
? result.entries
: []
).map(
(entry) =>
`${entry?.type === 'directory' ? '[目录]' : '[文件]'} ${entry?.name ?? ''}`,
);
return [
{
type: 'text',
text: lines.join('\n') || '(空目录)',
},
];
}
case 'write_file':
return [
{
type: 'text',
text:
`已通过 MindSpace 写入 ${result.relativePath ?? args?.path}` +
`${Number(result.sizeBytes ?? 0)} 字节,package ${result.packageId ?? MINDSPACE_PACKAGE_ID}`,
},
];
case 'edit_file':
return [
{
type: 'text',
text:
`已通过 MindSpace 编辑 ${result.relativePath ?? args?.path}` +
`package ${result.packageId ?? MINDSPACE_PACKAGE_ID}`,
},
];
case 'generate_long_image':
return [
{
type: 'text',
text: JSON.stringify(
{
relativePath:
result.relativePath,
canonicalUrl:
result.canonicalUrl,
packageId:
result.packageId,
sizeBytes:
result.sizeBytes,
},
null,
2,
),
},
];
case 'write_binary_file':
return [
{
type: 'text',
text:
`已通过 MindSpace 写入二进制文件 ${result.relativePath ?? args?.path}` +
`${Number(result.sizeBytes ?? 0)} 字节,package ${result.packageId ?? MINDSPACE_PACKAGE_ID}`,
},
];
case 'create_dir':
return [
{
type: 'text',
text:
`已通过 MindSpace 创建目录 ${result.relativePath ?? args?.path}`,
},
];
case 'publish_page':
return [
{
type: 'text',
text: JSON.stringify(
{
relativePath:
result.relativePath,
canonicalUrl:
result.canonicalUrl,
packageId:
result.packageId,
},
null,
2,
),
},
];
default:
throw new Error(
`Unsupported logical workspace tool: ${name}`,
);
}
}
async function callTool(name, args) {
if (ALLOWED_TOOLS && !ALLOWED_TOOLS.has(name)) {
throw new Error(`工具 ${name} 未授权`);
}
if (
LOGICAL_WORKSPACE_ENABLED &&
LOGICAL_WORKSPACE_TOOLS.has(name)
) {
return callLogicalWorkspaceTool(
name,
args,
);
}
if (name === 'publish_page') {
throw new Error(
'publish_page: MindSpace scoped workspace RPC 未配置',
);
}
switch (name) {
case 'read_file': {
const abs = resolveSandboxed(args.path);
@@ -634,7 +871,48 @@ async function callTool(name, args) {
throw new Error('generate_docx: title 不能为空');
}
const sections = args.sections ?? args.payload?.sections;
const result = runGenerateDocxScript({ outputPath, title, sections });
let result;
if (LOGICAL_WORKSPACE_ENABLED) {
const tempDir = fs.mkdtempSync(
path.join(
os.tmpdir(),
'mindspace-docx-',
),
);
try {
result = runGenerateDocxScript({
outputPath,
physicalOutputPath:
path.join(
tempDir,
'generated.docx',
),
title,
sections,
});
await callLogicalWorkspaceTool(
'write_binary_file',
{
path: outputPath,
bodyBase64:
result.body.toString(
'base64',
),
},
);
} finally {
fs.rmSync(tempDir, {
recursive: true,
force: true,
});
}
} else {
result = runGenerateDocxScript({
outputPath,
title,
sections,
});
}
return [
{
type: 'text',