Files
john 4e21ca937a
Deploy Documentation / deploy (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
CI / H5 Plaza Tests and Build (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Add TKMind platform extensions, H5/MindSpace stack, and deployment tooling.
Fork goose with custom MCP widgets, platform extensions (aider, git, web, search),
MindSpace H5 backend/frontend, Plaza/Ops UIs, and deploy scripts for tkmind.cn.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 21:30:20 +08:00

185 lines
7.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import fs from 'node:fs';
import path from 'node:path';
import { SYSTEM_CATEGORIES } from './mindspace.mjs';
import { PUBLISH_ROOT_DIR, resolvePublishDir } from './user-publish.mjs';
/** 用户 MindSpace 下的分区子目录(与上传分类一致) */
export const UPLOAD_ZONE_CODES = ['oa', 'private', 'public'];
export function resolveMindspaceStorageRoot(h5Root, env = process.env) {
return path.resolve(env.MINDSPACE_STORAGE_ROOT ?? path.join(h5Root, 'data', 'mindspace'));
}
/** Agent 工作区 = MindSpace/<username>/ */
export function resolveUserWorkspaceRoot(h5Root, user) {
return resolvePublishDir(h5Root, user);
}
export const WORKSPACE_HINTS_FILENAME = '.tkmindhints';
export const LEGACY_WORKSPACE_HINTS_FILENAME = '.goosehints';
function renderBrandingBlock() {
return `## 品牌与称呼(硬性)
- 对用户统一称 **TKMind**,不要称 goose、Goose、goosed
- 不要描述本工作区为「Rust goose 项目」或「goose AI 框架」
- 本工作区是 TKMind **MindSpace 用户空间**,用于 OA/私人/公开文件管理与静态页面生成
`;
}
export function resolveZoneDir(workspaceRoot, categoryCode) {
return path.join(workspaceRoot, categoryCode);
}
export function resolveZoneFilePath(workspaceRoot, categoryCode, filename) {
return path.join(resolveZoneDir(workspaceRoot, categoryCode), filename);
}
export function zoneLabel(categoryCode) {
return SYSTEM_CATEGORIES.find((item) => item.code === categoryCode)?.name ?? categoryCode;
}
export function ensureUserZoneDirs(workspaceRoot) {
for (const code of UPLOAD_ZONE_CODES) {
fs.mkdirSync(resolveZoneDir(workspaceRoot, code), { recursive: true });
}
}
/** 上传完成后镜像到 MindSpace/<user>/<category>/文件名 */
export function mirrorAssetToZone({ workspaceRoot, categoryCode, filename, sourcePath }) {
if (!UPLOAD_ZONE_CODES.includes(categoryCode)) return null;
if (!sourcePath || !fs.existsSync(sourcePath)) return null;
ensureUserZoneDirs(workspaceRoot);
const dest = resolveZoneFilePath(workspaceRoot, categoryCode, filename);
fs.mkdirSync(path.dirname(dest), { recursive: true });
fs.copyFileSync(sourcePath, dest);
return dest;
}
export function removeZoneMirror({ workspaceRoot, categoryCode, filename }) {
if (!UPLOAD_ZONE_CODES.includes(categoryCode)) return;
const target = resolveZoneFilePath(workspaceRoot, categoryCode, filename);
if (fs.existsSync(target)) fs.unlinkSync(target);
}
export function isPathInsideUserWorkspace(workspaceRoot, requestedPath) {
const base = path.resolve(workspaceRoot);
const resolved = path.resolve(requestedPath);
return resolved === base || resolved.startsWith(`${base}${path.sep}`);
}
export function renderUserSpaceHints({ username, workspaceRoot }) {
const zoneLines = UPLOAD_ZONE_CODES.map(
(code) => `- \`${code}/\`${zoneLabel(code)}(用户上传落在此分区)`,
);
return `# TKMind 用户空间分区
你是用户 \`${username}\` 的专属助手。当前会话工作区(唯一文件根目录):
\`${workspaceRoot}\`
${renderBrandingBlock()}
## 分区规则(硬性)
用户上传的文件**只会**出现在下列子目录之一:
${zoneLines.join('\n')}
## 查找 / 读取文件
1. 在工作区内搜索,例如 \`tree oa/\`\`find oa -name '*.csv'\`\`cat oa/文件名.csv\`
2. 用户说「OA 区」→ \`oa/\`;「私人区」→ \`private/\`;「公开区」→ \`public/\`
3. **禁止**访问其它用户目录、\`${PUBLISH_ROOT_DIR}/\` 根目录(非本用户)、\`data/mindspace/\`、主机绝对路径
4. **禁止**用公网 URL 列目录或读 CSV;公网链接仅用于分享已发布 HTML
5. 找不到时说明该分区内没有该文件,请用户确认上传到了哪个分区
## 生成页面
- 静态 HTML 可写入工作区根目录或 \`public/\`,完成后给出公网链接
## 工作区文件与 OA 界面
- 写入 \`oa/\`\`private/\`\`public/\` 根目录的支持类型文件(docx、csv、pdf、图片等)会**自动同步**到 MindSpace 资产库
- 打开对应分区或保存文件后会出现在界面中,可直接预览或下载
- 用户上传的文件仍以界面入库为准,并镜像到上述分区
`;
}
export function buildUserSpaceConstraints({ username, workspaceRoot, publicBaseUrl, slug }) {
const zoneList = UPLOAD_ZONE_CODES.map((code) => `\`${code}/\`${zoneLabel(code)}`).join('、');
return [
'## TKMind 用户空间分区(硬性约束)',
'',
'- 对用户统一称 **TKMind**;禁止称 goose / Goose / goosed 或「Rust goose 项目」',
`- 用户 \`${username}\` 的 Agent 工作区:\`${workspaceRoot}\``,
`- 用户上传落在分区子目录:${zoneList}(例如 OA 文件在 \`oa/\``,
'- **查找文件**:只在上述工作区内搜索(如 `oa/2025-12-06T13-34_export.csv`',
'- **禁止**:访问其它用户目录、MindSpace 根目录、data/mindspace 内部路径、主机绝对路径',
'- **禁止**用公网 URL 列目录或读 CSV;生成 HTML 写入工作区并给出公网链接',
publicBaseUrl && slug
? `- 公网 HTML 前缀(仅分享用):\`${publicBaseUrl}/${PUBLISH_ROOT_DIR}/${slug}/\``
: null,
]
.filter(Boolean)
.join('\n');
}
export function ensureUserSpaceHints(workspaceRoot, context) {
const hintsPath = path.join(workspaceRoot, WORKSPACE_HINTS_FILENAME);
const legacyPath = path.join(workspaceRoot, LEGACY_WORKSPACE_HINTS_FILENAME);
const content = renderUserSpaceHints({ ...context, workspaceRoot });
const existing = fs.existsSync(hintsPath) ? fs.readFileSync(hintsPath, 'utf8') : '';
if (existing !== content) {
fs.writeFileSync(hintsPath, content, 'utf8');
}
if (fs.existsSync(legacyPath)) {
fs.unlinkSync(legacyPath);
}
return hintsPath;
}
export async function syncUserZonesFromAssets(pool, storageRoot, userId, workspaceRoot) {
ensureUserZoneDirs(workspaceRoot);
const placeholders = UPLOAD_ZONE_CODES.map(() => '?').join(', ');
const [rows] = await pool.query(
`SELECT a.original_filename, c.category_code, v.storage_key
FROM h5_assets a
JOIN h5_space_categories c ON c.id = a.category_id AND c.user_id = a.user_id
JOIN h5_asset_versions v ON v.id = a.current_version_id
WHERE a.user_id = ? AND a.status <> 'deleted' AND c.category_code IN (${placeholders})`,
[userId, ...UPLOAD_ZONE_CODES],
);
for (const row of rows) {
const sourcePath = path.join(storageRoot, row.storage_key);
mirrorAssetToZone({
workspaceRoot,
categoryCode: row.category_code,
filename: row.original_filename,
sourcePath,
});
}
return { workspaceRoot, mirrored: rows.length };
}
export async function ensureUserSpaceLayout({
pool,
storageRoot,
userId,
username,
publicBaseUrl,
slug,
workspaceRoot,
}) {
ensureUserZoneDirs(workspaceRoot);
if (pool && userId) {
await syncUserZonesFromAssets(pool, storageRoot, userId, workspaceRoot);
}
const context = { username: username ?? slug, workspaceRoot, slug, publicBaseUrl };
ensureUserSpaceHints(workspaceRoot, context);
return {
workspaceRoot,
zonesRoot: workspaceRoot,
zonesPublicDir: resolveZoneDir(workspaceRoot, 'public'),
constraints: buildUserSpaceConstraints(context),
...context,
};
}