fix: require docx generation for downloadable pages

This commit is contained in:
john
2026-07-01 12:07:46 +08:00
parent 6cb1a2475d
commit ee3dd3b23c
9 changed files with 88 additions and 4 deletions
+29
View File
@@ -5,9 +5,11 @@ import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export const PUBLISH_SKILL_NAME = 'static-page-publish';
export const DOCX_SKILL_NAME = 'docx-generate';
export const PUBLISH_ROOT_DIR = 'MindSpace';
export const PUBLIC_ZONE_DIR = 'public';
export const PUBLISH_SKILL_DIR = path.join(__dirname, 'skills', PUBLISH_SKILL_NAME);
export const DOCX_SKILL_DIR = path.join(__dirname, 'skills', DOCX_SKILL_NAME);
export const WORKSPACE_HINTS_FILENAME = '.tkmindhints';
export const LEGACY_WORKSPACE_HINTS_FILENAME = '.goosehints';
@@ -155,6 +157,14 @@ description: 在专属 MindSpace 目录生成可公开访问的静态 HTML 报
5. 保存 HTML 后,服务端会**立即**生成同名预览图 \`<文件名>.thumbnail.svg\`(Agent 交互阶段即生效,无需等用户保存到「我的空间」)
6. 完成后按「回复格式」返回可点击链接
## Word 下载页(必须)
- 如果用户明确要求页面里**可下载 Word / docx**,必须先 \`load_skill\`\`docx-generate\`
- 用 \`.agents/skills/docx-generate/generate_docx.py\` 生成 \`public/文件名.docx\`
- 生成后必须用 \`list_dir public\`(或等价方式)确认目标 \`.docx\` 已存在,再写 \`public/页面.html\`
- HTML 中只能使用同目录相对路径链接文档,例如 \`<a href="report.docx" download>下载 Word 文档</a>\`
- **禁止**只写下载按钮或 \`.docx\` 链接,却没有先把目标文档落盘
## 回复格式(必须)
向用户交付页面时,**必须使用 Markdown 可点击链接**,让用户在聊天里直接点开预览:
@@ -222,6 +232,7 @@ description: 在专属 MindSpace 目录生成可公开访问的静态 HTML 报
- 不要写入当前工作区以外的任何目录
- shell 仅用于本目录内整理文件/简单脚本;不要 \`rm -rf\` 越界路径、不要安装系统级依赖
- **禁止**在 HTML 中用 \`data:...;base64,...\` 内嵌 Word/PDF;二进制文件单独落盘后用相对路径链接(见 \`docx-generate\` 技能)
- 用户要求 Word / docx 下载时,禁止跳过 \`docx-generate\` 直接在 HTML 中伪造下载链接
## 示例
@@ -257,6 +268,12 @@ ${renderBrandingBlock(addressName)}
- **禁止**用公网 URL、curl、wget 或 read_image 去「列目录」「读 CSV」——静态站点不提供目录索引
- 有 \`shell\` 时用 \`ls oa/\`\`find .\`\`cat oa/file.csv\`;有 \`tree\` 时用 \`tree oa/\`
- 不要说「我没有 shell」——若系统提示已列出 shell,就必须用 shell 在工作区内操作
## Word 下载页
- 若用户要求页面里可下载 Word / docx:必须先 \`load_skill\`\`docx-generate\`
- 先生成并确认 \`public/*.docx\` 已存在,再写 \`public/*.html\`
- HTML 中只能用同目录相对路径链接该文档,禁止只写下载按钮却没有先把 `.docx` 落盘
`;
}
@@ -296,6 +313,7 @@ export function buildSandboxSessionConstraints({ baseConstraints, developerTools
'## 生成 / 发布 HTML 页面',
'- 你有 write_file/edit_file 工具:**必须由你**写入 `public/xxx.html`(或工作区根目录 `.html`',
'- 开始前执行 load_skill → `static-page-publish`,按技能说明写入 mindspace-cover 元数据',
'- 如果用户要求 Word / docx 下载:先 `load_skill` → `docx-generate`,生成并确认 `public/*.docx` 已存在,再写 HTML 用相对路径链接',
'- **禁止**用 shell / cat / heredoc / echo / cp 写入 HTMLshell 在容器内执行,文件不会出现在公网 MindSpace 路径',
'- **禁止**让用户手动保存到 public 或说无法生成页面(除非 write_file 调用失败)',
'- 完成后回复 `[页面标题](公网URL)` 可点击链接;写入 `public/` 时 URL 必须含 `/public/` 路径段',
@@ -317,6 +335,7 @@ export function buildPublishConstraints({ slug, username, publicBaseUrl, publish
'- **禁止**:访问 `assets/` 内部路径、其它用户目录、主机绝对路径;禁止用公网 URL 列目录或读 CSV',
'- **路径规则**:只用相对路径;禁止 `../`;工作区外的路径会被系统拒绝(OS 层强制,非软约束)',
'- **生成页面(必须亲自完成)**:先 `load_skill` → `static-page-publish`,再用 `write_file`/`edit_file` 写入 `public/页面.html`',
'- **Word 下载页(必须亲自完成)**:若用户要求 Word / docx 下载,必须先 `load_skill` → `docx-generate`,生成并确认 `public/*.docx` 已存在,再在 HTML 中用相对路径链接',
'- **用户可见回复**:不要向用户复述 load_skill、技能更新、页脚标记、mindspace-cover 等内部实现;完成后直接给出页面链接或结果',
'- **禁止**用 shell 写入 HTML**禁止**让用户「手动保存到 public 目录」或说「我无法生成页面」——除非 write_file 已失败并报告错误',
'- 完成后给出 Markdown 可点击公网链接 `[标题](URL)`;写入 `public/页面.html` 时 URL 为 `.../MindSpace/<用户ID>/public/页面.html`',
@@ -335,7 +354,17 @@ export function ensureWorkspaceHintsInstalled(publishDir, context) {
return hintsPath;
}
export function ensureDocxSkillInstalled(publishDir) {
if (!fs.existsSync(DOCX_SKILL_DIR)) return null;
const skillRoot = path.join(publishDir, '.agents', 'skills', DOCX_SKILL_NAME);
fs.rmSync(skillRoot, { recursive: true, force: true });
fs.mkdirSync(path.dirname(skillRoot), { recursive: true });
fs.cpSync(DOCX_SKILL_DIR, skillRoot, { recursive: true });
return path.join(skillRoot, 'SKILL.md');
}
export function ensurePublishSkillInstalled(publishDir, context) {
ensureDocxSkillInstalled(publishDir);
const skillRoot = path.join(publishDir, '.agents', 'skills', PUBLISH_SKILL_NAME);
fs.mkdirSync(skillRoot, { recursive: true });
const skillPath = path.join(skillRoot, 'SKILL.md');