feat: add generate_docx sandbox MCP tool for public Word downloads
Expose generate_docx in mindspace-sandbox-mcp so agents can write public/*.docx before linking HTML download pages, with tests mirroring the Mark summary flow. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,6 +4,49 @@ import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { syncPublicDocxDownloads } from './mindspace-public-finish-sync.mjs';
|
||||
|
||||
function copyDocxGenerateSkill(root) {
|
||||
const srcDir = path.join(process.cwd(), 'skills', 'docx-generate');
|
||||
const destDir = path.join(root, '.agents', 'skills', 'docx-generate');
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
for (const name of ['generate_docx.py', 'SKILL.md']) {
|
||||
fs.copyFileSync(path.join(srcDir, name), path.join(destDir, name));
|
||||
}
|
||||
}
|
||||
|
||||
function summarySections(caseName) {
|
||||
return [
|
||||
{
|
||||
heading: '一、项目概述',
|
||||
paragraphs: [
|
||||
`${caseName}项目面向医疗服务流程中的核心痛点,通过数字化与 AI 能力重构诊前、诊中、诊后体验。`,
|
||||
'本摘要基于创新三角评估框架,从创新、成果、市场、团队四个维度提炼关键结论。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '二、解决方案与技术创新',
|
||||
paragraphs: [
|
||||
'项目以数据驱动和智能辅助决策为主线,形成可复制的技术体系与流程再造方案。',
|
||||
'在技术创新、商业模式与政策吻合度方面均具备行业示范价值。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '三、成果与价值',
|
||||
paragraphs: [
|
||||
'核心指标显示候诊效率、临床质量或安全水平获得显著提升,患者与医护双侧受益。',
|
||||
'项目已具备向医联体与区域平台输出的标准化能力。',
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: '四、壁垒、风险与未来展望',
|
||||
paragraphs: [
|
||||
'竞争壁垒来自临床数据积累、流程嵌入深度与专科Know-how。',
|
||||
'未来可在专科深化、基层赋能与支付模式创新方向持续扩展。',
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function startSandbox(root, envOverrides = {}) {
|
||||
const child = spawn(process.execPath, ['mindspace-sandbox-mcp.mjs', root], {
|
||||
@@ -102,3 +145,75 @@ test('sandbox MCP exposes schedule tools only when schedule env is configured',
|
||||
['schedule_create_item', 'schedule_create_reminder', 'schedule_list_items'],
|
||||
);
|
||||
});
|
||||
|
||||
test('generate_docx writes public Word files for Mark-style summary request', async (t) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mindspace-sandbox-docx-'));
|
||||
fs.mkdirSync(path.join(root, 'public'), { recursive: true });
|
||||
copyDocxGenerateSkill(root);
|
||||
|
||||
const server = startSandbox(root, {
|
||||
ALLOWED_TOOLS: 'generate_docx,write_file,list_dir',
|
||||
});
|
||||
t.after(() => server.child.kill());
|
||||
|
||||
await server.request('initialize');
|
||||
const listed = await server.request('tools/list');
|
||||
assert.ok(listed.result.tools.some((tool) => tool.name === 'generate_docx'));
|
||||
|
||||
const cases = [
|
||||
{
|
||||
docx: 'public/协和智慧门诊研究摘要.docx',
|
||||
html: 'public/协和智慧门诊研究摘要.html',
|
||||
title: '北京协和医院「智慧门诊 + AI 预问诊」研究摘要',
|
||||
},
|
||||
{
|
||||
docx: 'public/广州妇儿CDSS研究摘要.docx',
|
||||
html: 'public/广州妇儿CDSS研究摘要.html',
|
||||
title: '广州妇儿中心「数据驱动 + AI 临床决策」研究摘要',
|
||||
},
|
||||
];
|
||||
|
||||
for (const item of cases) {
|
||||
const generated = await server.request('tools/call', {
|
||||
name: 'generate_docx',
|
||||
arguments: {
|
||||
output_path: item.docx,
|
||||
title: item.title,
|
||||
sections: summarySections(item.title),
|
||||
},
|
||||
});
|
||||
assert.equal(generated.result.isError, false, generated.result.content?.[0]?.text);
|
||||
assert.match(generated.result.content[0].text, /已生成 public\/.+\.docx/);
|
||||
assert.ok(fs.statSync(path.join(root, item.docx)).size > 500);
|
||||
}
|
||||
|
||||
for (const item of cases) {
|
||||
const docxName = path.basename(item.docx);
|
||||
await server.request('tools/call', {
|
||||
name: 'write_file',
|
||||
arguments: {
|
||||
path: item.html,
|
||||
content: `<!doctype html><html><body><a href="${docxName}" download>下载 Word</a></body></html>`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const listedPublic = await server.request('tools/call', {
|
||||
name: 'list_dir',
|
||||
arguments: { path: 'public' },
|
||||
});
|
||||
assert.equal(listedPublic.result.isError, false);
|
||||
assert.match(listedPublic.result.content[0].text, /协和智慧门诊研究摘要\.docx/);
|
||||
assert.match(listedPublic.result.content[0].text, /广州妇儿CDSS研究摘要\.docx/);
|
||||
|
||||
const sync = syncPublicDocxDownloads({ publishDir: root });
|
||||
assert.deepEqual(sync.missing, []);
|
||||
assert.deepEqual(sync.synced, []);
|
||||
|
||||
for (const item of cases) {
|
||||
const abs = path.join(root, item.docx);
|
||||
assert.ok(fs.existsSync(abs));
|
||||
const zipHeader = fs.readFileSync(abs).subarray(0, 2).toString('utf8');
|
||||
assert.equal(zipHeader, 'PK', `${item.docx} should be a valid zip/docx`);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user