一次将行业 SaaS 向上抽象为通用平台的真实重构记录。
背景:垂直系统的天花板
我们最初构建的是一套智能投标管理系统——面向政府采购、工程招标场景,集成了 AI 辅助写作、知识库检索、合规审查等能力。系统运行良好,但暴露了一个结构性问题:
本质上,我们把一个通用写作引擎做成了垂直工具。
重构目标只有一个:抽象领域层,让平台与场景解耦。
WriteOS
├── Workbench(前端) ← Cursor 式三栏 IDE 布局
│ ├── FileExplorer(左栏) ← 文件树 + 技能包管理
│ ├── Editor(中栏) ← Markdown 编辑器
│ └── AgentPanel(右栏) ← Agent 工作过程实时可视化
├── Skills System(后端) ← 可插拔技能包,动态加载
│ ├── skill.json 清单 ← 技能包元数据
│ └── executors/ ← 技能执行器
└── Workspace Service(后端) ← 本地文件读写 + 项目管理核心设计原则:平台不感知业务,业务通过技能包注入平台。
参考 Cursor / Windsurf 的 IDE 布局,将写作工作台设计为三栏结构:
┌──────────┬──────────────────────────┬────────────────┐
│ 文件树 │ 编辑器(Markdown) │ Agent 活动 │
│ │ │ │
│ 📁 项目 │ # 第三章 施工组织设计 │ ▼ 分析上下文 ✓ │
│ 📄 大纲 │ │ 读取大纲(12节) │
│ 📄 标书 │ [AI 流式生成中...] │ │
│ │ │ ▷ 知识库搜索 ⟳ │
│ 技能包 │ │ 搜"施工部署" │
│ ✅ 标书 │ │ 找到 3 个案例 │
│ □ 小说 │ │ │
└──────────┴──────────────────────────┴────────────────┘
│ > /生成章节 施工部署方案... [发送] │
└─────────────────────────────────────────────────────┘工程实现:使用 React + Zustand 管理三栏状态,workbenchStore 统一维护当前项目、文件树、Agent 步骤列表。
为什么不用现有编辑器组件直接嵌入?
现有的 BidCompose 页面与标书数据模型强耦合,直接复用等于把问题带过来。选择提取核心编辑逻辑,写一个轻量的 WorkbenchEditor,仅处理 Markdown 内容本身。
原有系统的 Agent 技能以硬编码方式注册:
# 旧方式:硬编码映射
_SKILL_EXECUTORS = {
"generate_section_content": ContentExecutor,
"review_section": ReviewSectionExecutor,
# ... 12 个技能全部写死
}新架构引入 skill.json 清单,每个技能包是独立目录:
{
"id": "bid-writing",
"name": "标书写作",
"version": "1.0.0",
"executors": {
"generate_section_content": "executors/content_executor.py:ContentExecutor",
"generate_outline": "executors/outline_executor.py:OutlineExecutor"
},
"slash_commands": {
"/生成": "generate_section_content",
"/审查": "review_section",
"/大纲": "generate_outline"
}
}新增小说写作场景,只需添加 skills/novel-writing/skill.json,平台自动发现。零侵入现有代码。
API 端点同步暴露技能包管理能力:
GET /api/skills/packs # 列出已安装技能包
POST /api/skills/packs/install # 安装技能包
DELETE /api/skills/packs/{id} # 卸载写作过程中,Agent 会调用多个工具(知识库搜索、内容生成、图表插入)。原系统将这些过程隐藏在 loading 动画后,用户无法了解 AI 在做什么。
WriteOS 将 SSE 事件流结构化为可视化步骤:
// SSE 事件格式(新增字段)
{
"type": "tool_start",
"step_id": "step-uuid",
"tool_name": "search_knowledge",
"tool_args_summary": "搜索: 施工组织设计",
"progress": 30
}前端 useAgentStream hook 实时解析事件,驱动 AgentPanel 展示每个工具调用的状态(pending / running / done / error):
▼ 分析上下文 ✓ done
读取大纲,共 12 节
▷ 知识库搜索 ⟳ running
搜 "施工部署方案"
[████████░░░░] 65%
○ 内容生成 待执行
deepseek-chat这一设计借鉴了 Claude Code 的工具调用可视化理念:让用户理解 AI 的推理过程,而非只看结果。
原系统所有内容存储在 PostgreSQL,用户无法直接操作文件。WriteOS 引入 WorkspaceService,将本地目录作为项目载体:
class WorkspaceService:
def create_project(self, name, skill_pack) -> WorkspaceProject:
# 在本地创建目录,元数据写入 .writeos_projects.json
...
def _resolve_safe_path(self, project_id, file_path) -> Path:
# 路径穿越防护:确保操作在项目目录内
base = Path(project.path).resolve()
target = (base / file_path).resolve()
if not str(target).startswith(str(base)):
return None # 拒绝越界访问用户可以在 Finder / 文件管理器直接访问项目文件,用任意编辑器打开 .md 文件,而不是被锁在平台内。
本次重构使用多 Agent 并行开发:团队协调者负责拆分任务,后端 Agent 和前端 Agent 同时工作,最终合并。
新增/修改文件共 19 个,主要变更:
层 | 新增文件 | 作用 |
|---|---|---|
后端 | workspace_service.py | 本地文件管理 |
后端 | workspace_routes.py | REST API |
后端 | skills_routes.py | 技能包管理 API |
后端 | skills/*/skill.json | 技能包清单 |
前端 | Workbench.tsx | 三栏主页面 |
前端 | AgentPanel.tsx | Agent 活动面板 |
前端 | workbenchStore.ts | 全局状态 |
前端 | useAgentStream.ts | SSE 解析 Hook |
关键原则:不破坏现有标书功能。所有新增代码在原有系统之上叠加,/workbench 路由独立于现有业务路由,技能包系统兼容原有执行器。
这次重构让我们重新思考"通用平台"与"垂直工具"的边界:
真正无法通用的,只有领域知识——提示词、模板、关键词规则。而这些,正是 skill.json 负责封装的部分。
useAgentStream 接入现有 assistant_routes SSE 流WriteOS 的核心假设是:好的 AI 写作工具,应该像 IDE 一样强大,像文本编辑器一样轻盈。这次重构是验证这个假设的第一步。