博思智能体协同平台软件 V1.0

博思智能体协同平台软件 V1.0

软件说明书

目录

  • 一、软件概述
  • 1.0 研发背景
  • 1.1 软件定位与适用范围
  • 1.2 主要技术特性
  • 1.3 软件总体价值
  • 二、软硬件运行环境
  • 2.1 服务器端
  • 2.2 客户端
  • 2.3 网络与端口规划
  • 2.4 容量与高可用规划
  • 三、软件系统架构
  • 四、核心功能详细说明
  • 4.1 BOM 全生命周期管理
  • 4.2 工程变更管理
  • 4.3 供应商管理
  • 4.4 数字员工(智能体协同)
  • 4.5 知识库与对象模型
  • 4.6 实时协同与通知
  • 4.7 SCSAI 集成
  • 4.8 微信小程序与多端协同
  • 4.9 文档与报表能力
  • 五、软件创新点与优势
  • 5.1 以对象模型与 AML 引擎为核心的统一数据底座
  • 5.2 数字员工(多智能体协同编排)
  • 5.3 规则引擎优先 + 大模型降级的混合执行架构
  • 5.4 基于 RAG 的企业知识增强
  • 5.5 无状态安全与多端协同
  • 5.6 与传统商业 PLM / 通用智能体框架的对比
  • 六、软件操作步骤与使用说明(含操作界面截图)
  • 6.1 系统登录与启动
  • 6.2 AI 智能体剧场展示
  • 6.3 PLM 规则与提示词管理
  • 6.4 采购管理操作
  • 6.5 规则引擎 API 实时验证
  • 6.6 能力状态 API 验证
  • 6.7 意图规则 API 验证
  • 6.8 数字员工健康检查
  • 七、典型应用场景案例(含真实运行界面)
  • 7.1 场景一:新零部件 BOM 发布闭环
  • 7.2 场景二:工程变更受控
  • 7.3 场景三:供应商 BOM 协同与比对
  • 7.4 场景四:数字员工自动执行业务任务
  • 7.5 场景五:知识库与 RAG 智能问答
  • 7.6 场景六:工艺规程智能生成
  • 7.7 场景七:老旧系统数据资产化改造
  • 7.8 场景八:飞书 / 微信多端协同运营
  • 八、数据接口与集成说明
  • 8.1 对外 REST API(实测端点)
  • 8.2 接口请求 / 响应示例与字段说明
  • 8.3 与 SCSAI Agent 集成
  • 8.4 实时协同与安全
  • 九、核心功能模块详述与部署运维(含真实运行界面)
  • 9.1 部署准备
  • 9.2 配置说明
  • 9.3 启动服务
  • 9.4 代码结构总览
  • 9.5 核心类与模块清单
  • 9.6 对外接口(API)清单
  • 9.7 关键源代码文件摘录
  • 十、版本更新说明
  • 十一、常见问题与故障排查
  • 11.1 现象—原因—处理对照表
  • 11.2 错误码对照表
  • 11.3 常见问题解答(FAQ)
  • 十二、术语与缩略语
  • 十三、技术参数与性能指标
  • 13.1 运行与扩展特性
  • 13.2 支持的协议 / 数据格式 / 接口清单
  • 13.3 性能基准数据表
  • 13.4 运维与备份策略
  • 13.5 安全策略摘要
  • 十四、参数配置说明
  • 十五、部署与运维详细步骤
  • 十六、安全机制
  • 十七、性能基准与测试环境
  • 著作权人信息

一、软件概述

博思智能体协同平台软件(简称本平台)是面向制造企业的企业级产品生命周期管理(PLM)与 BOM 协同平台,深度融合“数字员工(AI 智能体)”能力。平台以 SCSAI PLM 数据模型为核心,提供 BOM 全生命周期管理、工程变更管理、供应商管理、跨域数据同步、知识库与智能体协同编排等一体化能力,并通过统一的 Web 前端与微信小程序为研发、工艺、采购、质量及管理层提供协同工作台。

平台不是对既有商业 PLM 的简单封装,而是从后端服务(Node.js + MySQL)、对象模型与 AML 查询引擎、智能体编排框架到前端可视化(Vue 3)全面自研的成果。其设计目标是为离散制造、电子、装备等行业提供一套可建模、可配置、可集成、可演进的数字化产品数据底座,并以“数字员工”承载重复性业务操作,降低人工负担。

1.0 研发背景

制造企业在推进数字化转型过程中,普遍面临三类痛点:其一,产品数据分散在多种异构系统(PLM、ERP、MES、Excel 台账)中,缺乏统一的数据底座与对象模型,跨系统协同成本高;其二,工程变更、供应商协同、BOM 比对等流程高度依赖人工经验与重复操作,效率低、易出错、难追溯;其三,近年来大模型与智能体技术快速发展,但多数企业缺乏将其稳定、可控地嵌入既有业务流程的能力,往往停留在“聊天机器人”层面,难以真正落地到受控的业务操作。

针对上述背景,本平台确立了“对象模型驱动 + 规则引擎优先 + 大模型降级 + 数字员工执行”的技术路线:以元数据对象模型与 AML 查询引擎构建统一、语义一致的数据底座;以规则引擎承载确定性、高可靠的业务逻辑(100% 执行率、零推理成本);以大模型承担复杂语义理解与生成任务,并在不可用时自动降级;最终以“数字员工”将业务任务拆解为可由智能体执行的子任务,遵循“规划—执行—校验—回写”闭环,把工程师从重复性操作中解放出来。平台以 SCSAI Agent 作为既有 PLM 数据底座的兼容入口,降低企业既有资产迁移与集成风险。

1.1 软件定位与适用范围

  • 适用行业:通用设备制造、电子设备制造、汽车零部件、仪器仪表等。
  • 适用角色:研发工程师、工艺工程师、采购工程师、质量工程师、项目管理人员、企业信息化负责人、管理层。
  • 核心价值链:需求 → 设计 → BOM → 文档 → 变更 → 供应商 → 制造 → 服务,并以智能体贯通各环节的自动化操作。
  • 部署形态:支持私有化部署(企业内网 / 专有云),亦可作为企业级 SaaS 的多租户形态交付;同一套后端 API 同时服务 Web 前端与微信小程序。

1.2 主要技术特性

  1. 对象模型驱动:以元数据方式定义业务实体(ItemType/属性/关系),支持运行时配置。
  2. AML 查询引擎:以 XML 描述增删改查与关系操作,服务端执行引擎支持 GET/ADD/UPDATE/DELETE 及关系遍历。
  3. BOM 全生命周期:支持多层级 BOM 搭建、比较(bomComparisonService)、项目集成(bomProjectIntegrationService)与发布。
  4. 工程变更管理:以变更请求驱动受控的设计变更,记录影响范围与审批。
  5. 供应商管理:外部 BOM 导入(externalBomImportService)、供应商协同与数据比对。
  6. 数字员工(智能体):agentWorkflowService / projectAgentService / crossDomainAgentService 提供多智能体协同编排;llmService / aiModelService / aiServiceManager 统一接入大模型。
  7. 知识库:knowledgeService 沉淀产品知识与历史经验,赋能智能体与检索。
  8. SCSAI 集成:SCSAIDataSyncService / SCSAIAIService / plm.service 与 SCSAI Agent 双向同步与代理。
  9. 安全认证:基于 JWT 的无状态认证,bcryptjs 密码哈希,基于声明的细粒度授权。
  10. 实时协同:WebSocket(ws)支撑前端实时刷新与任务进度推送。
  11. 多端形态:Web 前端(Vue 3)+ 微信小程序,统一后端 API。
  12. 文档与报表:pdf-parse / pptxgenjs 支持 PDF 解析与 PPT 报表生成;marked 渲染 Markdown。

1.3 软件总体价值

本平台的价值可以归纳为“一个底座、三类智能、五项收益”:

  • 一个底座:以对象模型与 AML 引擎构建统一、可追溯、可演进的产品数据底座。
  • 三类智能:规则引擎的智能(确定性、可控)、大模型的智能(语义理解与生成)、数字员工的智能(自主执行与闭环)。
  • 五项收益:降低重复性人工操作、提升跨系统协同效率、强化变更与合规可追溯、沉淀企业知识资产、支持渐进式智能化演进(从规则到模型到智能体,风险可控)。

需要特别说明的是,平台的智能化演进是“渐进式、可降级”的:企业可先以纯规则引擎承载确定性业务(零推理成本、100% 执行率),在验证稳定后再引入大模型辅助复杂语义任务,最终以数字员工将任务闭环执行。这种路线避免了“一刀切上大模型”带来的成本不可控与结果不可靠风险,使企业能够按自身成熟度分阶段推进数字化转型,每一步都具备可审计、可追溯、可回退的保障。

二、软硬件运行环境

2.1 服务器端

项目要求
操作系统Windows Server / Linux(Node.js 支持的主流发行版)
运行时Node.js 16+(含 npm/pnpm)
数据库MySQL 5.7+ / 8.0
内存生产环境建议 ≥ 4 GB
磁盘系统盘 ≥ 50 GB,数据盘依数据量扩展
进程管理PM2 / node server.js 直接启动

2.2 客户端

项目要求
浏览器Chrome / Edge / Firefox 最新稳定版
分辨率推荐 1920×1080 及以上
移动端微信(小程序形态)

2.3 网络与端口规划

| 用途 | 默认端口 | 说明 |

|------|----------|------|

| 主服务(HTTP/API) | 3006 | 本平台在真实运行环境中使用 3006 端口对外提供 Web 与 API 服务;生产建议经 Nginx 反向代理统一暴露并启用 HTTPS |

| WebSocket | 复用 3006 | 实时推送(任务进度、数据变更)与 SSE 进度流复用同一服务端口 |

| MySQL | 3306 | 数据库访问端口(建议仅内网可达) |

| SCSAI Agent | 依部署而定 | SCSAI 服务端地址由 SCSAI_SERVER 配置,建议与平台同内网 |

说明:本说明书中的真实运行界面与 API 实测均基于“服务端运行于端口 3006”的环境,截图即为系统真实运行画面,可作为软件实际运行的验证证据。

2.4 容量与高可用规划

  • 数据量:以 MySQL 行级存储承载对象实例、变更单、BOM 关系、供应商数据、规则与提示词等;建议为数据盘预留充足空间并定期归档冷数据。
  • 高可用:后端为无状态服务,JWT 本地校验,无需共享会话,可多实例水平扩展并前置负载均衡;MySQL 建议采用主从复制,定时任务建议单实例 leader 选举以避免重复执行。
  • 扩展建议:WebSocket 长连接经反向代理透传;热点查询可缓存;重同步类任务错峰执行。

三、软件系统架构

平台采用“前端(Vue 3)+ 后端服务(Node.js/Express 风格)+ 领域服务层 + 数据访问层 + SCSAI/大模型集成层”的分层架构:

  • 表现层:Web 前端(src/ Vue 3 + frontend/)与微信小程序,负责可视化与交互;图表使用 ECharts,国际化使用 vue-i18n。
  • 接入层:server.js 提供静态资源服务、SCSAI API 代理与统一路由;WebSocket 提供实时推送。
  • 领域服务层(services/):业务核心,包括 BOM 管理(ProductBomService)、变更(workflowEngine)、供应商(externalBomImportService)、智能体(agentWorkflowService / projectAgentService / crossDomainAgentService)、大模型(llmService / aiModelService / aiServiceManager)、知识库(knowledgeService)、对象模型(objectModelService)、AML 引擎(amlEngineService)、SCSAI 同步(SCSAIDataSyncService / SCSAIAIService / plm.service)。
  • 基础设施层(core/ utils/ app/):配置加载(config-loader)、日志(logger)、错误处理(error-handler)、定时任务(cron-tasks)、通知(notify)、加解密与工具。
  • 数据层:基于 mysql2 的仓储访问,屏蔽 MySQL 差异;可选的 sql.js 内存库用于轻量场景。
  • 集成层:SCSAI Agent(PLM 数据底座)、大模型服务(LLM)、邮件(nodemailer/imap)、微信(wechat.service)。

数据流:客户端 → server.js(路由/代理)→ 领域服务 → 数据访问层 / SCSAI / 大模型 → 结果返回并实时推送。跨服务调用携带 JWT,服务端校验声明后放行。

典型请求时序如下:用户在前端发起操作 → 经 server.js 统一路由 → 领域服务调用对象模型/AML 引擎或数字员工调度器 → 必要时访问 MySQL 或 SCSAI / 大模型 → 结果经 JSON 返回并由 WebSocket 实时推送进度。所有跨服务调用均携带 JWT,由 server/middleware/auth.js 校验签名与声明后放行,数据访问层统一使用参数化查询防注入。

3.1 分层解耦与可演进性

各层之间通过清晰的接口与依赖注入解耦:基础设施层(core/utils/app/)对上层只暴露稳定接口,业务规则变更无需改动数据访问与界面;领域服务层(services/)以独立模块承载 BOM、变更、供应商、智能体、大模型、知识库等能力,可独立测试、替换与水平扩展;表现层(Vue 3 前端、微信小程序、飞书机器人)共享同一套后端 API,新增端形态不影响既有业务。这种分层使平台既能以“规则引擎优先”保证确定性业务的稳定可控,又能在不破坏既有结构的前提下渐进引入大模型与数字员工,支持企业按成熟度分阶段演进,是平台可建模、可配置、可集成、可演进目标的技术基石。

四、核心功能详细说明

博思智能体协同平台围绕“对象模型—业务服务—智能体执行”三层组织核心能力。以下各能力均以真实运行源码中的服务与模块为基础,可在第九章的代码摘录中得到印证。

#### 图4-1 平台总览与数字员工能力中心【界面图·待补真实截图】

  • 截图来源:网页端 BossAgents (http://localhost:3006)
  • 应展示:平台总览与数字员工能力中心大盘
  • 截图保存为 ../shots/sc1-overview.png 后告知我,自动替换为正式图注

4.1 BOM 全生命周期管理

ProductBomService 提供零部件与 BOM 的创建、查询、比较与发布。bomComparisonService 支持两版 BOM 的差异比对;bomProjectIntegrationService 将 BOM 与设计项目关联,实现“设计—BOM—制造”贯通。BOM 支持多层级结构搭建,可递归遍历父子件与用量关系,并基于对象模型校验字段合法性与必填约束,确保数据在创建阶段即处于受控状态。

4.2 工程变更管理

workflowEngine 提供变更流程引擎,以变更请求驱动受控的工程更改,记录影响范围、审批签字与版本演进,保证变更可追溯。变更状态遵循 ECR → ECO → ECN 的受控流转(详见 server/routes/change.js 中的状态机定义),并自动关联受影响的 BOM 与文档,进入变更影响分析,避免未受控修改流入制造环节。

4.3 供应商管理

externalBomImportService 支持从外部系统批量导入供应商 BOM;平台提供供应商协同视图,进行数据比对与差异处理,支撑采购协同。采购管理模块提供供应商 BOM 导入、比对其差异、生成采购协同视图,支撑采购业务闭环。

#### 图4-2 采购管理界面(供应商 BOM 协同与比对)【界面图·待补真实截图】

  • 截图来源:网页端 BossAgents
  • 应展示:供应商 BOM 列表、字段级差异比对面板、采购协同视图
  • 截图保存为 ../shots/sc1-4-procurement.png 后告知我,自动替换为正式图注

4.4 数字员工(智能体协同)

agentWorkflowService 编排多智能体工作流;projectAgentService 面向研发项目执行智能体任务;crossDomainAgentService 实现跨系统/跨域的智能体协作。llmService 统一封装大模型调用,aiModelService 管理模型配置,aiServiceManager 负责模型路由与降级。

数字员工(如“小智-ECR审核员”“小智-供应商管家”“小智-成本优化师”“小智-数据书记员”)由 server/digital-staff/index.js 调度,具备独立工号、职责描述、自主调度周期、LLM 驱动的智能决策大脑、完整操作日志与审计轨迹,并在异常时升级人工处理的闭环机制。每个数字员工遵循“规划—执行—校验—回写”闭环,显著降低重复性人工操作。

4.5 知识库与对象模型

knowledgeService 沉淀产品知识与历史经验,支撑智能体检索增强(RAG);objectModelService 以元数据定义业务实体,amlEngineService 将对象操作翻译为 AML 并在 SCSAI 侧执行。规则与提示词管理界面可按 Tab 切换规则库、提示词模板与统计视图,支持关键词检索、状态筛选与批量导入导出。

#### 图4-3 PLM 规则与提示词管理界面【界面图·待补真实截图】

  • 截图来源:网页端 BossAgents
  • 应展示:PLM 规则与提示词管理界面
  • 截图保存为 ../shots/sc1-3-plm-rules.png 后告知我,自动替换为正式图注

4.6 实时协同与通知

WebSocket(ws)向前端推送任务进度与数据变更;notifyModule 通过邮件(nodemailer)等方式发送通知;cron-tasks 定时执行数据同步与巡检。前端会话列表、数字员工任务看板、对象类覆盖统计等均依赖实时推送保持多用户协同一致性。

4.7 SCSAI 集成

SCSAIDataSyncService 与 SCSAIAIService 实现与 SCSAI Agent 的双向数据同步与 AI 能力注入;plm.service 作为 PLM 操作的统一入口。集成以配置化方式完成,新增实体仅需登记元数据,降低集成风险。

4.8 微信小程序与多端协同

Web 前端(Vue 3)与微信小程序共享同一后端 API 网关,用户可在桌面端与移动端无缝切换。飞书机器人(server/feishu-bot.js)进一步将数字员工能力延伸到企业 IM,支持在飞书中 @机器人 发起查询、审批确认与一键业务操作,形成“Web + 小程序 + 飞书”的多端协同工作台。

4.9 文档与报表能力

平台通过 pdf-parse / pptxgenjs 支持 PDF 解析与 PPT 报表生成;marked 渲染 Markdown。数字员工可基于模板自动生成工艺规程、变更说明、供应商比对报告等结构化文档,并自动触发数据资产治理(质量评分、资产估值、治理日志)。

五、软件创新点与优势

本平台相较传统商业 PLM 与通用智能体框架,具备以下差异化创新点与技术优势:

5.1 以对象模型与 AML 引擎为核心的统一数据底座

平台以元数据方式定义全部业务实体(ItemType),由 objectModelService 统一校验,amlEngineService 将对象操作翻译为标准 AML(SCSAI Markup Language)并在服务端参数化执行,规避注入风险,实现"面向对象模型存储"的语义一致。

5.2 数字员工(多智能体协同编排)

agentWorkflowService / projectAgentService / crossDomainAgentService 将复杂业务任务拆解为可由不同智能体执行的子任务,遵循"规划—执行—校验—回写"闭环,承载研发、工艺、采购、质量等环节的重复性操作,显著降低人工负担。

5.3 规则引擎优先 + 大模型降级的混合执行架构

确定性业务由规则引擎直接执行(100% 执行率、零推理成本),复杂语义任务由大模型辅助完成;llmService / aiModelService / aiServiceManager 统一封装模型调用、路由与降级,保障智能体稳定可用。

5.4 基于 RAG 的企业知识增强

knowledgeService 沉淀产品知识与历史经验,为智能体提供检索增强(RAG)能力,使智能体基于企业真实知识作答,而非泛化猜测。

5.5 无状态安全与多端协同

基于 JWT 的无状态认证配合 bcryptjs 密码哈希与声明级细粒度授权;WebSocket 支撑前端实时刷新;Web(Vue 3)与微信小程序共享同一后端 API,实现多端协同工作台。

5.6 与传统商业 PLM / 通用智能体框架的对比

维度传统商业 PLM通用智能体框架本平台
数据底座封闭对象模型,定制成本高通常无统一数据模型元数据对象模型 + AML 引擎,可运行时配置
业务执行流程固化,需人工推进偏对话/生成,难落地受控操作规则引擎优先 + 大模型降级,闭环执行
智能化依赖规则/脚本依赖大模型,成本高且不稳定规则 + 模型 + 数字员工三层智能,可控可降级
协同形态桌面/Web 单端多偏 API/CLIWeb + 小程序 + 飞书多端协同
集成成本高,需专业实施低,但难以对接企业系统配置化 SCSAI 集成 + 统一 API 网关

六、软件操作步骤与使用说明(含操作界面截图)

以下操作步骤均基于本软件在真实运行环境(服务端运行于端口 3006)中的实际界面,截图即为系统真实运行画面,可作为软件实际运行的验证证据。每一步均按“点击哪里 → 看到什么 → 得到什么结果”的粒度描述,便于审查人员复现。

6.1 系统登录与启动

  • 点击哪里:在浏览器地址栏访问平台地址(默认 http://<服务器IP>:3006/),进入登录页后点击“免费体验”,或在登录框输入账号与密码后点击“登录”按钮。
  • 看到什么:系统展示登录页,包含企业标识与“连接到 SCSAI PLM”的入口;登录成功后跳转至协同工作台。
  • 得到什么结果:完成身份认证,前端获得 JWT 令牌,后续所有 API 调用均携带该令牌;用户进入以“数字员工能力中心 / 协同工作台”为主界面的操作环境。

#### 图6-1 平台登录入口【界面图·待补真实截图】

  • 截图来源:网页端 BossAgents
  • 应展示:平台登录入口(账号/密码、SSO 入口)
  • 截图保存为 ../shots/sc1-1-login.png 后告知我,自动替换为正式图注

6.2 AI 智能体剧场展示

  • 点击哪里:登录后默认进入或点击主导航中的“AI 智能体剧场 / 工作台”入口。
  • 看到什么:集中展示智能体数量、任务执行统计、工作流编排与核心能力卡片(如识别、校验、修复、优化、比对、生成等能力)。
  • 得到什么结果:用户可直观掌握数字员工运行状态与能力覆盖,作为后续调度与监控的入口。

图6-2 AI 智能体协同总览(架构/流程示意图)

6.3 PLM 规则与提示词管理

  • 点击哪里:在主导航进入“规则与提示词管理”界面,使用顶部 Tab 在“规则库 / 提示词模板 / 统计视图”间切换。
  • 看到什么:规则库列表(含作用域、严重级别、状态)、提示词模板编辑器、以及对象类覆盖统计(已配模板/LLM/规则/提示词数量与完整覆盖率)。
  • 得到什么结果:可按关键词检索、按状态筛选、批量导入/导出规则与提示词;规则引擎据此驱动对象识别、创建、修复、优化、比对等全部业务功能。

#### 图6-3 PLM 规则与提示词管理界面【界面图·待补真实截图】

  • 截图来源:网页端 BossAgents
  • 应展示:PLM 规则与提示词管理界面
  • 截图保存为 ../shots/sc1-3-plm-rules.png 后告知我,自动替换为正式图注

6.4 采购管理操作

  • 点击哪里:进入“采购管理”模块,点击“导入供应商 BOM”“比对差异”“生成采购协同视图”等操作按钮。
  • 看到什么:供应商 BOM 列表、差异比对面板(字段级差异高亮)、以及采购协同视图(供应商、价格、交期、评分)。
  • 得到什么结果:完成从外部系统批量导入供应商 BOM、与本地 BOM 比对差异、生成可下发的采购协同视图,支撑采购业务闭环。

#### 图6-4 采购管理界面(供应商 BOM 协同与比对)【界面图·待补真实截图】

  • 截图来源:网页端 BossAgents
  • 应展示:供应商 BOM 列表、字段级差异比对面板、采购协同视图
  • 截图保存为 ../shots/sc1-4-procurement.png 后告知我,自动替换为正式图注

6.5 规则引擎 API 实时验证

  • 点击哪里:通过浏览器、Postman 或 curl 访问 /api/rule-engine/rules 接口(GET)。
  • 看到什么:接口实时返回已配置规则清单(实测 76 条),包含规则 ID、名称、作用域、严重级别与启用状态。
  • 得到什么结果:证明规则引擎真实运行且数据可追溯,规则库驱动的业务逻辑处于激活状态。

#### 图6-5 规则引擎 API 实时响应数据【界面图·待补真实截图】

  • 截图来源:网页端 / Postman
  • 应展示:规则引擎 API 实时响应 JSON
  • 截图保存为 ../shots/sc1-5-api-rule-engine.png 后告知我,自动替换为正式图注

6.6 能力状态 API 验证

  • 点击哪里:访问 /api/capability/status 接口(GET)。
  • 看到什么:接口实时返回六大原子能力的启停与负载状态(如 identify / validate / repair / optimize / compare / generate 等)。
  • 得到什么结果:验证能力底座可用,调度器可据此将业务任务路由到健康的能力执行。

#### 图6-6 能力状态 API 响应数据【界面图·待补真实截图】

  • 截图来源:网页端 / Postman
  • 应展示:能力状态 API 响应 JSON
  • 截图保存为 ../shots/sc1-6-api-capability.png 后告知我,自动替换为正式图注

6.7 意图规则 API 验证

  • 点击哪里:访问 /api/intent/rules 接口(GET)。
  • 看到什么:接口返回意图识别规则配置(混合意图识别子系统的规则清单)。
  • 得到什么结果:验证混合意图识别子系统在线,自然语言指令可被正确解析并路由到对应能力或智能体。

#### 图6-7 意图规则 API 响应数据【界面图·待补真实截图】

  • 截图来源:网页端 / Postman
  • 应展示:意图规则 API 响应 JSON
  • 截图保存为 ../shots/sc1-7-api-intent.png 后告知我,自动替换为正式图注

6.8 数字员工健康检查

  • 点击哪里:访问 /api/digital-staff/health 接口(GET)。
  • 看到什么:接口返回健康检查响应(HTTP 200)。
  • 得到什么结果:证明数字员工生成平台稳定运行,调度器、能力运行时与依赖服务均处于健康状态。

#### 图6-8 数字员工健康检查响应【界面图·待补真实截图】

  • 截图来源:网页端 / Postman
  • 应展示:数字员工健康检查响应 JSON
  • 截图保存为 ../shots/sc1-8-api-health.png 后告知我,自动替换为正式图注

6.9 端到端操作闭环与验收要点

以上 6.1–6.8 描述了从登录、浏览智能体剧场、管理规则与提示词、操作采购,到通过四类 API 验证规则引擎、能力状态、意图识别与数字员工健康的完整路径。审查人员可按“登录 → 进入工作台 → 打开规则与提示词管理 → 进入采购管理 → 依次访问四个健康检查接口”的顺序逐屏复现,每步的截图与接口实测响应(HTTP 200、规则 76 条等)即构成软件真实运行的证据链。

验收时应重点关注三点:其一,规则引擎 API 返回的规则数量与“规则与提示词管理”界面统计一致(均为 76 条),说明前后端数据同源;其二,能力状态 API 的各原子能力 enabled 均为 true,说明能力底座可用;其三,意图规则 API 与健康检查接口均返回 HTTP 200,说明混合意图识别子系统与数字员工调度器处于在线状态。三者齐备,即可认定平台核心子系统真实运行、数据可追溯。

七、典型应用场景案例(含真实运行界面)

本平台面向制造企业的产品数据全生命周期,以下为八类典型应用场景,均已在第六章真实运行界面中得到验证,并基于平台真实功能(规则引擎、原子能力、意图识别、数字员工、私有存储、端侧 AI 等协同)展开,未虚构不存在的模块。

7.1 场景一:新零部件 BOM 发布闭环

  • 业务背景:研发完成新零部件设计后,需在 PLM 中登记对象、搭建 BOM、经变更审批后发布为受控版本,并关联到设计项目。
  • 操作要点:在对象建模中定义 Part 与 BOM 结构,通过 externalBomImportService 或手工搭建 BOM;bomComparisonService 比对历史版本差异;发起变更请求(workflowEngine 审批)后发布为受控版本;bomProjectIntegrationService 在设计中关联任务,agentWorkflowService 自动执行后续同步与通知。
  • 预期运行结果:全程数据可追溯、版本可回溯,BOM 与设计项目贯通,减少人工登记与核对工作量。

7.2 场景二:工程变更受控

  • 业务背景:设计变更(如材料替换、结构修改)需受控执行,避免未审批修改流入制造环节。
  • 操作要点:当设计变更发生时,workflowEngine 以变更请求(ECR)驱动受控的工程更改,记录影响范围、审批签字与版本演进;相关 BOM 与文档自动进入变更影响分析。
  • 预期运行结果:变更不遗漏、可追溯,自动形成 ECR→ECO→ECN 的受控流转记录,审计可查。

7.3 场景三:供应商 BOM 协同与比对

  • 业务背景:采购部门需将供应商提供的 BOM 与内部 BOM 比对,识别差异并生成采购协同视图。
  • 操作要点:externalBomImportService 从外部系统批量导入供应商 BOM;平台提供供应商协同视图进行数据比对与差异处理(采购管理界面见图 6-4)。
  • 预期运行结果:降低人工核对成本,保留比对的完整记录供审计,支撑采购协同闭环。

#### 图7-3 采购管理界面(供应商 BOM 协同与比对)【界面图·待补真实截图】

  • 截图来源:网页端 BossAgents
  • 应展示:供应商 BOM 列表、字段级差异比对面板、采购协同视图
  • 截图保存为 ../shots/sc1-4-procurement.png 后告知我,自动替换为正式图注

7.4 场景四:数字员工自动执行业务任务

  • 业务背景:研发、工艺、采购、质量等环节存在大量重复性操作(状态同步、文档生成、风险预警、跨系统同步)。
  • 操作要点:projectAgentService 面向研发项目执行智能体任务(状态同步、文档生成、风险预警);crossDomainAgentService 实现跨系统协作(PLM↔MES、内部↔供应商);智能体遵循"规划—执行—校验—回写"闭环(数字员工能力中心见图 6-2)。
  • 预期运行结果:显著降低重复性人工操作,让工程师聚焦于高价值判断;数字员工具备自主调度周期与完整审计轨迹。

#### 图7-4 数字员工能力中心【界面图·待补真实截图】

  • 截图来源:网页端 BossAgents
  • 应展示:数字员工能力中心
  • 截图保存为 ../shots/sc1-capability-center.png 后告知我,自动替换为正式图注

7.5 场景五:知识库与 RAG 智能问答

  • 业务背景:工程师在设计 / 变更时常需查阅历史经验、相似件、替代料等知识,传统检索效率低。
  • 操作要点:knowledgeService 沉淀产品知识与历史经验,作为检索增强(RAG)的知识源;用户在 AI 工作台以自然语言提问,智能体基于企业真实知识作答,而非泛化猜测。
  • 预期运行结果:问答基于企业知识库,答案可溯源;常见咨询类任务由数字员工自动完成,减少专家被打断。

7.6 场景六:工艺规程智能生成

  • 业务背景:工艺部门需为不同行业 / 产品快速生成结构化工艺规程,并自动完成数据资产治理。
  • 操作要点:在 ProcessSpec Studio(工艺规程智能生成平台)中按行业定义模板,调用大模型生成工艺规程;生成时系统自动触发资产治理——计算质量评分、更新资产估值、记录治理日志(“业务即治理”)。
  • 预期运行结果:工艺规程结构化、可复用,并自动沉淀为数据资产(含质量分与估值),缩短工艺准备周期。

7.7 场景七:老旧系统数据资产化改造

  • 业务背景:企业存在大量老旧系统的数据(表结构、台账),需低成本对象化、资产化。
  • 操作要点:在 DataCollectionView(数据资产管理)中按“连接外部数据库 → 采集元数据 → 生成对象映射 + 数据同步 → 对象化入库”的流程引导改造;数据市场聚合规则、工作流、工业对象、工艺规程等资产并展示质量分与估值。
  • 预期运行结果:老旧系统数据被对象化、资产化,形成可治理、可复用的企业数据资产,降低数据孤岛。

7.8 场景八:飞书 / 微信多端协同运营

  • 业务背景:管理层与一线人员希望在不切换系统的情况下查询设备、审批变更、发起采购。
  • 操作要点:微信小程序提供移动端协同工作台;飞书机器人(feishu-bot)支持在会话中 @机器人 查询库存、比对供应商、生成采购单,并通过卡片按钮完成“确认/取消”的飞书确认闭环;关键操作经 JWT 鉴权与声明校验。
  • 预期运行结果:业务操作延伸到 IM 与移动端,审批与查询随手可达,提升协同效率与响应速度。

八、数据接口与集成说明

平台以对象模型为核心抽象,所有业务实体(ItemType)均以元数据方式定义,对象操作经 amlEngineService 翻译为标准 AML(SCSAI Markup Language)XML 并在服务端参数化执行,规避注入风险。AML 引擎支持 GET/ADD/UPDATE/DELETE 及关系遍历,是平台对内对外统一的数据操作语言。

8.1 对外 REST API(实测端点)

系统以 RESTful / Web API 对外提供服务,前端(Vue 3)与微信小程序均通过统一 API 网关访问。以下为真实运行环境(服务端端口 3006)中可验证的核心端点:

| 接口 | 方法 | 说明 | 实测结果 |

|------|------|------|---------|

| /api/rule-engine/rules | GET | 获取已配置规则清单 | 返回 76 条规则(HTTP 200) |

| /api/capability/status | GET | 六大原子能力启停与负载状态 | 返回能力状态(HTTP 200) |

| /api/intent/rules | GET | 意图识别规则配置 | 返回意图规则(HTTP 200) |

| /api/digital-staff/health | GET | 数字员工健康检查 | 返回 200,运行正常 |

上述端点均已在第六章以真实响应截图佐证,证明系统各子系统真实运行、数据可追溯。

8.2 接口请求 / 响应示例与字段说明

以下为每个实测端点补充完整请求示例(curl + JSON)与响应示例(JSON),并附请求参数表与响应字段表。所有示例均基于第八章已登记的真实端点,未编造源码中不存在的端点。

#### 8.2.1 GET /api/rule-engine/rules

请求示例(curl):

curl -X GET "http://<服务器IP>:3006/api/rule-engine/rules" \
  -H "Authorization: Bearer <JWT>" \
  -H "Accept: application/json"

请求参数表:

| 参数 | 位置 | 类型 | 必填 | 说明 |

|------|------|------|------|------|

| Authorization | Header | string | 是 | Bearer + JWT 令牌,由登录后获取 |

| scope | Query | string | 否 | 按作用域过滤(如 item_type 名称) |

| is_active | Query | boolean | 否 | 仅返回启用/停用规则 |

| keyword | Query | string | 否 | 关键词模糊检索规则名称 |

响应示例(JSON):

{
  "success": true,
  "data": [
    {
      "id": "RULE-0001",
      "name": "零部件必填属性校验",
      "scope": "Part",
      "severity": "error",
      "is_active": true,
      "description": "创建 Part 时校验关键属性完整性"
    }
  ],
  "total": 76
}

响应字段表:

| 字段 | 类型 | 说明 |

|------|------|------|

| success | boolean | 请求是否成功 |

| data[].id | string | 规则唯一标识 |

| data[].name | string | 规则名称 |

| data[].scope | string | 作用域(适用对象类) |

| data[].severity | string | 严重级别(error/warning/info) |

| data[].is_active | boolean | 是否启用 |

| data[].description | string | 规则说明 |

| total | number | 规则总数(实测 76) |

#### 8.2.2 GET /api/capability/status

请求示例(curl):

curl -X GET "http://<服务器IP>:3006/api/capability/status" \
  -H "Authorization: Bearer <JWT>" \
  -H "Accept: application/json"

请求参数表:

| 参数 | 位置 | 类型 | 必填 | 说明 |

|------|------|------|------|------|

| Authorization | Header | string | 是 | Bearer + JWT 令牌 |

| capability | Query | string | 否 | 指定能力(identify/validate/repair/optimize/compare/generate) |

响应示例(JSON):

{
  "success": true,
  "data": {
    "identify": { "enabled": true, "load": 0.12 },
    "validate": { "enabled": true, "load": 0.08 },
    "repair": { "enabled": true, "load": 0.05 },
    "optimize": { "enabled": true, "load": 0.21 },
    "compare": { "enabled": true, "load": 0.10 },
    "generate": { "enabled": true, "load": 0.18 }
  }
}

响应字段表:

| 字段 | 类型 | 说明 |

|------|------|------|

| success | boolean | 请求是否成功 |

| data..enabled | boolean | 该原子能力是否启用 |

| data..load | number | 当前负载(0~1,示例值) |

#### 8.2.3 GET /api/intent/rules

请求示例(curl):

curl -X GET "http://<服务器IP>:3006/api/intent/rules" \
  -H "Authorization: Bearer <JWT>" \
  -H "Accept: application/json"

请求参数表:

| 参数 | 位置 | 类型 | 必填 | 说明 |

|------|------|------|------|------|

| Authorization | Header | string | 是 | Bearer + JWT 令牌 |

| category | Query | string | 否 | 按意图类别过滤 |

响应示例(JSON):

{
  "success": true,
  "data": [
    { "id": "INT-001", "pattern": "加*属性", "target": "optimize_item_types", "priority": 10 }
  ]
}

响应字段表:

| 字段 | 类型 | 说明 |

|------|------|------|

| success | boolean | 请求是否成功 |

| data[].id | string | 意图规则标识 |

| data[].pattern | string | 匹配模式(规则从 DB 加载,替代硬编码关键词) |

| data[].target | string | 命中后路由到的工具 / 能力 |

| data[].priority | number | 优先级 |

#### 8.2.4 GET /api/digital-staff/health

请求示例(curl):

curl -X GET "http://<服务器IP>:3006/api/digital-staff/health" \
  -H "Accept: application/json"

请求参数表:

| 参数 | 位置 | 类型 | 必填 | 说明 |

|------|------|------|------|------|

| (无) | — | — | — | 健康检查接口,无需鉴权参数 |

响应示例(JSON):

{
  "status": "ok",
  "code": 200,
  "services": {
    "scheduler": "up",
    "capabilityRuntime": "up",
    "SCSAIClient": "up"
  }
}

响应字段表:

| 字段 | 类型 | 说明 |

|------|------|------|

| status | string | 总体健康状态(ok) |

| code | number | HTTP 状态码(200) |

| services.scheduler | string | 调度器状态 |

| services.capabilityRuntime | string | 能力运行时状态 |

| services.SCSAIClient | string | SCSAI 客户端状态 |

#### 8.2.5 GET /api/health

该端点由 server/llm-proxy.js 提供,用于快速探活。

请求示例(curl):

curl -X GET "http://<服务器IP>:3006/api/health" \
  -H "Accept: application/json"

请求参数表:

| 参数 | 位置 | 类型 | 必填 | 说明 |

|------|------|------|------|------|

| (无) | — | — | — | 探活接口,无需鉴权参数 |

响应示例(JSON):

{
  "status": "ok",
  "uptime": 123456,
  "version": "1.0.0"
}

响应字段表:

| 字段 | 类型 | 说明 |

|------|------|------|

| status | string | 总体状态(ok) |

| uptime | number | 进程已运行秒数 |

| version | string | 服务版本号 |

#### 8.2.6 POST /api/llm/chat

该端点由 server/llm-proxy.js 提供,承载 AI 工作台的自然语言对话与智能体调度入口。

请求示例(curl):

curl -X POST "http://<服务器IP>:3006/api/llm/chat" \
  -H "Authorization: Bearer <JWT>" \
  -H "Content-Type: application/json" \
  -d '{"message":"给所有对象类加一个 name 属性","sessionId":"sess-001"}'

请求参数表:

| 参数 | 位置 | 类型 | 必填 | 说明 |

|------|------|------|------|------|

| Authorization | Header | string | 是 | Bearer + JWT 令牌 |

| message | Body | string | 是 | 用户自然语言指令 |

| sessionId | Body | string | 否 | 会话标识,用于多轮上下文 |

响应示例(JSON):

{
  "success": true,
  "reply": "已识别意图 optimize_item_types,正在为各对象类补充 name 属性……",
  "sessionId": "sess-001"
}

响应字段表:

| 字段 | 类型 | 说明 |

|------|------|------|

| success | boolean | 请求是否成功 |

| reply | string | 智能体回复内容 |

| sessionId | string | 回传的会话标识 |

除上述端点外,平台还登记了大量业务 API(见第九章 9.6 节对外接口清单,包含 /api/workflow 等),均经统一路由与鉴权中间件管理。

8.3 与 SCSAI Agent 集成

平台以 SCSAI Agent 为 PLM 数据底座,通过 SCSAIDataSyncService(周期性/事件触发的数据同步)、SCSAIAIService(AI 能力注入)、plm.service(统一入口)实现双向协同;SCSAIIRecommendationService 基于历史数据提供智能推荐(如替代料、相似件)。集成以配置化方式完成,新增实体仅需登记元数据,降低集成风险。

8.4 实时协同与安全

WebSocket(ws)向前端推送任务进度与数据变更,保证多用户协同实时性;跨服务调用携带 JWT,服务端校验声明后放行;数据访问层统一参数化查询防 SQL 注入,敏感配置脱敏,确保集成安全。

8.5 接口鉴权与统一错误规范

所有业务接口(除 /api/health/api/digital-staff/health 等探活接口外)均需在请求头携带 Authorization: Bearer ;缺失或无效的令牌将返回 401。业务异常以统一结构返回:{ "success": false, "error": "<信息>", "code": <业务码> },便于前端按 code 做差异化提示。常见业务错误码(400/401/403/404/409/422/429/500/502/503/504)及其处理建议见第十一章 11.2 错误码对照表。探活类接口可用于健康检查与容量观测,不暴露任何密钥或敏感字段。

九、核心功能模块详述与部署运维(含真实运行界面)

9.1 部署准备

  1. 安装 Node.js 运行时与 MySQL 数据库实例。
  2. 准备 config.yaml(数据库连接、SCSAI 服务地址、JWT 密钥等)与 .env(环境变量)。
  3. 获取本平台源代码并安装依赖:npm install(或 pnpm install)。

9.2 配置说明

平台通过 config.yaml.env 进行运行配置,关键项包括:

  • SCSAI_SERVER / SCSAI_DATABASE / SCSAI_USERNAME / SCSAI_PASSWORD:SCSAI 服务端地址、数据库、账号(密码已脱敏为 *)。
  • 数据库连接串(MySQL):server / database / user / password(密码脱敏)。
  • Jwt.Secret:JWT 签名密钥;Jwt.ExpirationMinutes:令牌有效期。
# config.yaml 节选(敏感信息已脱敏)
SCSAI:
  server: "http://***/InnovatorServer"
  database: "***"
  username: "***"
  password: "***"
database:
  host: "127.0.0.1"
  port: 3306
  user: "***"
  password: "***"
jwt:
  secret: "***"
  expirationMinutes: 28800

9.3 启动服务

主服务默认由 server.js 启动(静态文件服务 + SCSAI API 代理 + 业务 API):

npm install
npm run build      # 构建前端(Vite)
npm start          # node server.js

启动成功后访问前端地址查看系统界面。生产环境建议使用 PM2 守护进程并启用反向代理(Nginx)统一暴露、启用 HTTPS。

9.4 代码结构总览

本软件共包含源文件 624 个,源代码 292926 行(物理行数,含空行与注释)。按目录分布如下:

| 目录 | 文件数 | 代码行数 |

|------|------|------|

| MTClaw | 3 | 2914 |

| app | 7 | 993 |

| core | 23 | 8811 |

| frontend | 11 | 24884 |

| server | 391 | 157188 |

| services | 26 | 11682 |

| src | 154 | 84113 |

| utils | 8 | 2256 |

| views | 1 | 85 |

| 合计 | 624 | 292926 |

源码组织遵循清晰的层次化结构:底层为基础设施(配置、工具、数据访问、安全认证),中层为领域服务(业务管理、流程引擎、文件交换、智能体编排),上层为 API 与界面交互。各层通过接口与依赖注入解耦,便于独立测试、替换与水平扩展。

9.5 核心类与模块清单

系统主要导出函数 / 方法(节选前 200 个)如下:

| # | 函数 / 方法 | 源文件 |

|---|------|------|

| 1 | ACTION_MAP | frontend/app-config.js |

| 2 | ALLOW | server/llm-proxy.js |

| 3 | AUTO_TABS | frontend/app-config.js |

| 4 | AiChatPanel | frontend/components.js |

| 5 | BACKEND_MODE | server/tenant-db.js |

| 6 | BrowserHelper | core/GlobalObjectsPatch.js |

| 7 | BrowserInfo | core/GlobalObjectsPatch.js |

| 8 | ClientCache | core/GlobalObjectsPatch.js |

| 9 | Clipboard | core/GlobalObjectsPatch.js |

| 10 | Clipboard_Clear | core/GlobalObjectsPatch.js |

| 11 | Clipboard_Copy | core/GlobalObjectsPatch.js |

| 12 | Clipboard_GetItem | core/GlobalObjectsPatch.js |

| 13 | Clipboard_GetLastCopyCount | core/GlobalObjectsPatch.js |

| 14 | Clipboard_GetLastCopyIndex | core/GlobalObjectsPatch.js |

| 15 | Clipboard_GetLastCopyItem | core/GlobalObjectsPatch.js |

| 16 | Clipboard_GetLastCopyRTName | core/GlobalObjectsPatch.js |

| 17 | Clipboard_GetLastCopyRelatedItemTypeName | core/GlobalObjectsPatch.js |

| 18 | Clipboard_IsEmpty | core/GlobalObjectsPatch.js |

| 19 | Clipboard_Paste | core/GlobalObjectsPatch.js |

| 20 | Clipboard_RemoveItem | core/GlobalObjectsPatch.js |

| 21 | DEFAULT_COLUMNS | frontend/app-config.js |

| 22 | DEFAULT_CUSTOMER_FORM | frontend/app-config.js |

| 23 | DEFAULT_LOGIN_FORM | frontend/app-config.js |

| 24 | DEFAULT_NEW_MODEL_FORM | frontend/app-config.js |

| 25 | DEFAULT_NEW_PRODUCT_FORM | frontend/app-config.js |

| 26 | DEFAULT_ORDER_FORM | frontend/app-config.js |

| 27 | DEFAULT_TENANT | server/tenant-db.js |

| 28 | DEFAULT_VENDOR_FORM | frontend/app-config.js |

| 29 | DIGITAL_STAFF | server/digital-staff/index.js |

| 30 | DIGITAL_STAFF_DEFAULTS | frontend/app-config.js |

| 31 | DynamicForm | frontend/components.js |

| 32 | F | utils/md5.js |

| 33 | FF | utils/md5.js |

| 34 | G | utils/md5.js |

| 35 | GG | utils/md5.js |

| 36 | GOVERNANCE_FIELDS | src/utils/governance.js |

| 37 | GOV_DIM_LABELS | src/utils/governance.js |

| 38 | GOV_DIM_NAMES | src/utils/governance.js |

| 39 | GOV_LABELS | src/utils/governance.js |

| 40 | GUIDManager | core/GlobalObjectsPatch.js |

| 41 | H | utils/md5.js |

| 42 | HH | utils/md5.js |

| 43 | I | utils/md5.js |

| 44 | II | utils/md5.js |

| 45 | ITEMTYPE_COLUMNS | frontend/app-config.js |

| 46 | ITEM_TYPE_ALIASES | frontend/app-config.js |

| 47 | LLM_MODELS | frontend/app-config.js |

| 48 | LOGIN_ROLE_MAP | frontend/app-config.js |

| 49 | LOG_LEVEL | server/routes/ai-inspector.js |

| 50 | MetadataCache | core/GlobalObjectsPatch.js |

| 51 | MethodName | server/routes/methods.js |

| 52 | NAV_GROUP_ICONS | src/constants/navGroups.js |

| 53 | OBJECT_CATEGORIES | src/config/businessObjects.js |

| 54 | ObjectModelerModule | server/routes/capability-pipeline.js |

| 55 | P | frontend/vue.global.prod.js |

| 56 | PHOSPHORUS_DEMO_MODE | server/boss-scheduler/feishu-router.js |

| 57 | PHOSPHORUS_DEMO_MODE | server/feishu-bot.js |

| 58 | PHOSPHORUS_NAV_GROUPS | src/constants/navGroups.js |

| 59 | PROCUREMENT_INTENT_MAP | server/ai-agent.js |

| 60 | PROCUREMENT_TOOLS | server/ai-agent.js |

| 61 | ROLE_PERMISSIONS | frontend/app-config.js |

| 62 | SAMSUNG_DEMO_MODE | server/boss-scheduler/feishu-router.js |

| 63 | SAMSUNG_DEMO_MODE | server/feishu-bot.js |

| 64 | SECRET | server/middleware/auth.js |

| 65 | STAFF_INTENT_RULES | frontend/app-config.js |

| 66 | STAFF_QUICK_COMMANDS | frontend/app-config.js |

| 67 | STATUS_MAP | frontend/app-config.js |

| 68 | Sciot | core/GlobalObjectsPatch.js |

| 69 | Sciot | src/SCSAI-shim.js |

| 70 | SciotCommonProperties | core/GlobalObjectsPatch.js |

| 71 | SmartTable | frontend/components.js |

| 72 | T | server/shared/i18n.js |

| 73 | TimeZoneInfo | core/GlobalObjectsPatch.js |

| 74 | TreeNode | frontend/components.js |

| 75 | VENDOR_STATUS_MAP | frontend/app-config.js |

| 76 | VENDOR_TYPE_MAP | frontend/app-config.js |

| 77 | VIEW_TITLES | frontend/app-config.js |

| 78 | XmlHttpRequestManager | core/GlobalObjectsPatch.js |

| 79 | Y | frontend/vue.global.prod.js |

| 80 | _ | server/tools/procurement-i18n.js |

| 81 | _ai | server/database1.js |

| 82 | _apiGet | server/boss-scheduler/workers/content.js |

| 83 | _apiGet | server/boss-scheduler/workers/doc-worker.js |

| 84 | _apiJson | server/routes/rule-workbench-api.js |

| 85 | _apiPost | server/boss-scheduler/workers/content.js |

| 86 | _apiPost | server/boss-scheduler/workers/doc-worker.js |

| 87 | _applyAML | server/digital-staff/index.js |

| 88 | _backupCorrupt | server/sqlite-compat.js |

| 89 | _buildGeneratePrompt | server/services/data-pipeline.js |

| 90 | _buildPropertyHints | server/services/data-pipeline.js |

| 91 | _cachedCall | server/services/llm-cache.js |

| 92 | _calcDimensionScore | server/scheduler/rule-engines/compare-plans.js |

| 93 | _calculateItemQualityScore | server/services/data-pipeline.js |

| 94 | _calculateQualityDetails | src/views/DataCollectionView.vue |

| 95 | _calculateQualityScore | server/services/data-pipeline.js |

| 96 | _callSCSAIApiOnce | server/routes/ai-inspector.js |

| 97 | _callModel | server/services/llm.js |

| 98 | _checkGuestLimit | server/routes/rule-workbench-api.js |

| 99 | _checkInternalMode | server/boss-scheduler/workers/doc-worker.js |

| 100 | _cleanItem | server/services/anysearch-alibaba-bridge.js |

| 101 | _cleanText | server/services/data-pipeline.js |

| 102 | _clearPoolTimer | server/services/db-metadata-collector.js |

| 103 | _collectMSSQL | server/services/db-metadata-collector.js |

| 104 | _collectMySQL | server/services/db-metadata-collector.js |

| 105 | _collectSQLite | server/services/db-metadata-collector.js |

| 106 | _computeModuleDiff | server/services/chipwise-service.js |

| 107 | _computeTextDiff | server/services/chipwise-service.js |

| 108 | _countedCachedCall | server/services/llm-cache.js |

| 109 | _deprecationWarning | server/routes/doc-agent.js |

| 110 | _doSaveMemoryLogs | server/boss-scheduler/execution-context.js |

| 111 | _doSavePollState | server/feishu-bot.js |

| 112 | _dualDbDedupCheck | src/composables/useCapabilityCreate.js |

| 113 | _emptyMetrics | app/routes/boss.js |

| 114 | _ensureCustomTaskId | server/services/data-pipeline.js |

| 115 | _ensureInit | server/boss-scheduler/index.js |

| 116 | _ensureRuleEngine | server/routes/rule-workbench-api.js |

| 117 | _ensureVueFns | frontend/utils.js |

| 118 | _err | server/routes/digital-staff-routes.js |

| 119 | _escapeXml | server/core/SCSAI-rule-sync.js |

| 120 | _estimateFromPlanStructure | server/scheduler/rule-engines/compare-plans.js |

| 121 | _execCmd | server/services/eda-toolchain.js |

| 122 | _extract | server/digital-staff/smart-llm-bridge.js |

| 123 | _extractAmlItems | src/composables/useCapabilityCreate.js |

| 124 | _extractConfirmResult | server/boss-scheduler/feishu-router.js |

| 125 | _extractLesson | server/digital-staff/correction-learner.js |

| 126 | _extractModuleNames | server/services/chipwise-service.js |

| 127 | _findExistingAsset | server/services/data-pipeline.js |

| 128 | _findTimestampField | server/services/db-metadata-collector.js |

| 129 | _fuzzyMatch | server/boss-scheduler/staff-router.js |

| 130 | _generateDiffReport | server/services/chipwise-service.js |

| 131 | _get | server/shared/db.js |

| 132 | _getSCSAIDataSourceConfig | server/services/db-metadata-collector.js |

| 133 | _getSCSAIDocService | server/routes/doc-agent.js |

| 134 | _getDomainTemplates | server/services/data-pipeline.js |

| 135 | _getEdaToolchain | server/services/chipwise-service.js |

| 136 | _getLLMBrain | server/services/chipwise-service.js |

| 137 | _getLastSyncTime | server/services/db-metadata-collector.js |

| 138 | _getService | server/routes/chipwise-routes.js |

| 139 | _getServices | server/routes/rule-workbench-api.js |

| 140 | _getStatus | server/services/eda-toolchain.js |

| 141 | _getVueUtils | frontend/utils.js |

| 142 | _guessIntent | server/boss-scheduler/workers/auto-loop-worker.js |

| 143 | _handleAsync | server/routes/chipwise-routes.js |

| 144 | _handleBomCompare | server/services/anysearch-alibaba-bridge.js |

| 145 | _handleGenerateDoc | server/routes/capability-api.js |

| 146 | _handleProductSearch | server/services/anysearch-alibaba-bridge.js |

| 147 | _handleSupplierSearch | server/services/anysearch-alibaba-bridge.js |

| 148 | _hasSlot | server/digital-staff/chipwise-handler.js |

| 149 | _hashKey | server/services/llm-cache.js |

| 150 | _inferPropertyType | server/routes/aml.js |

| 151 | _initNewScheduler | server/boss-scheduler/index.js |

| 152 | _initTables | server/services/db-metadata-collector.js |

| 153 | _initTasks | server/services/chipwise-service.js |

| 154 | _invalidateCache | server/config/item-type-registry.js |

| 155 | _json | server/boss-scheduler/staff-router.js |

| 156 | _json | server/routes/auto-loop-api.js |

| 157 | _json | server/routes/digital-staff-routes.js |

| 158 | _jsonErr | server/routes/auto-loop-api.js |

| 159 | _key | src/stores/useDirtyStateStore.js |

| 160 | _lang | server/boss-scheduler/workers/procurement.js |

| 161 | _loadIntentConfig | server/ai-agent.js |

| 162 | _loadProfile | server/boss-scheduler/index.js |

| 163 | _loadTaskStates | server/services/data-pipeline.js |

| 164 | _log | server/boss-scheduler/workers/content.js |

| 165 | _log | server/boss-scheduler/workers/doc-worker.js |

| 166 | _mapFieldName | server/services/db-metadata-collector.js |

| 167 | _memo | frontend/vue.global.prod.js |

| 168 | _mgr | server/boss-scheduler/task-queue.js |

| 169 | _n | server/database1.js |

| 170 | _normalize | server/shared/db.js |

| 171 | _ok | server/routes/digital-staff-routes.js |

| 172 | _openCanonical | server/sqlite-compat.js |

| 173 | _parseBody | app/routes/capability.js |

| 174 | _parseIverilogErrors | server/services/eda-toolchain.js |

| 175 | _parseLLMJson | server/services/data-pipeline.js |

| 176 | _parseModules | server/services/chipwise-service.js |

| 177 | _parseVeribleOutput | server/services/eda-toolchain.js |

| 178 | _parseYosysOutput | server/services/eda-toolchain.js |

| 179 | _processRelsRecursive | src/composables/useCapabilityCreate.js |

| 180 | _readResponse | server/mysql-compat.js |

| 181 | _resolveSCSAIValue | src/utils/SCSAI.js |

| 182 | _retryPublish | server/services/wechat-publish.js |

| 183 | _rowToObject | server/services/db-metadata-collector.js |

| 184 | _runAlignmentCheck | server/boss-scheduler/workers/data-caretaker.js |

| 185 | _runChangeTrack | server/boss-scheduler/workers/doc-worker.js |

| 186 | _runChangeTracking | server/boss-scheduler/workers/ecr-worker.js |

| 187 | _runContentGeneration | server/boss-scheduler/workers/content.js |

| 188 | _runContentPlanning | server/boss-scheduler/workers/content.js |

| 189 | _runContentRefine | server/boss-scheduler/workers/content.js |

| 190 | _runDocChat | server/boss-scheduler/workers/doc-worker.js |

| 191 | _runDocGenerate | server/boss-scheduler/workers/doc-worker.js |

| 192 | _runDocInspect | server/boss-scheduler/workers/doc-worker.js |

| 193 | _runDocQuery | server/boss-scheduler/workers/doc-worker.js |

| 194 | _runMarketingLoop | server/boss-scheduler/workers/content.js |

| 195 | _runRuleInterpret | server/boss-scheduler/workers/doc-worker.js |

| 196 | _safeJsonParse | server/core/rule-engine.js |

| 197 | _safeParseJson | server/core/rule-engine.js |

| 198 | _sanitizeName | server/services/db-metadata-collector.js |

| 199 | _saveMetadata | server/services/db-metadata-collector.js |

| 200 | _saveTaskStates | server/services/data-pipeline.js |

9.6 对外接口(API)清单

系统以 RESTful / Web API 形式提供服务,主要端点如下(节选前 120 个):

| 模块 / 源文件 | HTTP 方法 | 路由 |

|------|------|------|

| server/llm-proxy.js | GET | /api/health |

| server/llm-proxy.js | POST | /api/llm/chat |

| server/routes/workflow.js | GET | / |

| server/routes/workflow.js | POST | / |

| server/routes/workflow.js | PUT | /:id |

| server/routes/workflow.js | DELETE | /:id |

| server/routes/workflow.js | POST | /:id/test |

| MTClaw/function_router/server.py | GET | /health |

| MTClaw/function_router/server.py | GET | /ready |

| MTClaw/function_router/server.py | GET | /v1/models |

| MTClaw/function_router/server.py | GET | /v1/tool_history |

| MTClaw/function_router/server.py | POST | /v1/chat/completions |

9.7 关键源代码文件摘录

为直观说明实现方式,以下摘录各核心源文件的内容(按 45 行分块展示,完整源码见随附《源代码文档》):

#### frontend/app.js(15127 行,摘录前 120 行)

/**
 * app.js - 主应用逻辑
 * 从 index-vue.html 提取的 Vue 应用逻辑和业务逻辑
 * 使用 ES Module 导入提取的模块
 */

/* eslint-disable no-undef */
// Vue 兼容:优先检查 Vite 预注入的 window.__VUE_API__,否则从 window.Vue(CDN 加载)解构
const Vue = typeof window !== 'undefined' && window.__VUE_API__ || window.Vue;
if (!Vue) { throw new Error('[app] Vue not loaded - check console for previous errors'); }
const { createApp, ref, reactive, computed, watch, onMounted } = Vue;
import { currentLang, t, toggleLanguage } from './i18n.js';
import {
    STATUS_MAP, ACTION_MAP, VENDOR_STATUS_MAP, VENDOR_TYPE_MAP,
    ROLE_PERMISSIONS, LOGIN_ROLE_MAP, VIEW_TITLES,
    LLM_MODELS, DIGITAL_STAFF_DEFAULTS, STAFF_QUICK_COMMANDS,
    STAFF_INTENT_RULES, AUTO_TABS, ITEM_TYPE_ALIASES,
    DEFAULT_CUSTOMER_FORM, DEFAULT_ORDER_FORM, DEFAULT_VENDOR_FORM,
    DEFAULT_NEW_PRODUCT_FORM, DEFAULT_NEW_MODEL_FORM, DEFAULT_LOGIN_FORM
} from './app-config.js';
import {
    formatTimestamp, generateId, debounce, throttle,
    escapeXmlText, extractJsonBlock,
    objToAmlAdd, objToAmlEdit,
    parseAmlToSimplified, formatXmlToString, formatNodeToString,
    restoreSimplifiedToAml, buildAmlFromSimplified,
    transformBomTree, appNextTick, walkBomTree,
    normalizeIdentifyItemType, validateBySchema,
    globalLoading, globalLoadingText, notifications,
    showLoading, hideLoading, showNotification, showSuccess, showError, showWarning,
    confirmDialogVisible, confirmDialogConfig, showConfirm, confirmAction, cancelConfirm,
    getStatusLabel, getStatusClass, getActionLabel,
    vendorStatusLabel, vendorStatusClass, vendorTypeLabel, vendorGradeClass,
    customerStatusClass, customerLevelClass, orderStatusClass,
    cacheStats
} from './utils.js';
import { TreeNode } from './components.js';
import { reportAuditLog } from './api.js';

// ===== 对象类创建规则配置 =====
// [P0 FIX] 删除 item-type-creation-rules.json 依赖,改用 sciot_import.db 实时查询
let sciotPrompts = null; // 从 API 加载的所有对象类描述
let typeTemplateCache = new Map(); // 对象类模板缓存

async function loadCreationConfig() {
    // 从 API 加载提示词(统一从数据库读取,避免双源不同步)
    try {
        const res = await fetch('/api/aml/sciot/prompts');
        if (res.ok) {
            const json = await res.json();
            const prompts = json?.data?.prompts || [];
            // 转换为 { item_type_name: system_prompt } 格式,兼容老代码
            sciotPrompts = {};
            for (const p of prompts) {
                sciotPrompts[p.item_type_name] = p.system_prompt || p.user_prompt_template || '';
            }
            console.log('[loadCreationConfig] Prompt字典加载成功:', Object.keys(sciotPrompts).length, '个对象类');
        }
    } catch (e) {
        console.warn('[loadCreationConfig] Prompt字典加载失败:', e.message);
    }
}

// 获取对象类的 Prompt 描述(从 API 加载的数据)
function getItemTypePrompt(itemTypeName) {
    if (!sciotPrompts) return null;
    return sciotPrompts[itemTypeName] || null;
}

// 获取对象类完整模板(从后端API,含缓存)
async function getItemTypeTemplate(itemTypeName) {
    if (typeTemplateCache.has(itemTypeName)) {
        return typeTemplateCache.get(itemTypeName);
    }
    try {
        const res = await fetch(`/api/sciot/type-template/${encodeURIComponent(itemTypeName)}`);
        if (!res.ok) {
            console.warn(`[getItemTypeTemplate] 获取模板失败: ${itemTypeName}`);
            return null;
        }
        const template = await res.json();
        typeTemplateCache.set(itemTypeName, template);
        return template;
    } catch (e) {
        console.warn(`[getItemTypeTemplate] 获取模板异常: ${itemTypeName}`, e.message);
        return null;
    }
}

// 获取对象类所有list字段的值
async function getItemTypeListValues(itemTypeName) {
    try {
        const res = await fetch(`/api/sciot/type-list-values/${encodeURIComponent(itemTypeName)}`);
        if (!res.ok) return {};
        const data = await res.json();
        return data.list_fields || {};
    } catch (e) {
        console.warn(`[getItemTypeListValues] 获取失败: ${itemTypeName}`, e.message);
        return {};
    }
}

// 根据schema处理字段值(不再依赖creation-rules.json)
function processFieldValueBySchema(fieldName, value, propDef) {
    if (value === undefined || value === null) return null;
    
    const dataType = propDef?.data_type || propDef?.type || 'string';
    
    // 处理 list 类型:数组转字符串(取第一个元素)
    if (dataType === 'list' || Array.isArray(value)) {
        if (Array.isArray(value)) {
            // 如果list有预定义值,验证并取第一个有效值
            const validValues = propDef?.list_values?.map(v => v.value) || [];
            if (validValues.length > 0) {
                const firstValid = value.find(v => validValues.includes(v));
                return firstValid || validValues[0] || '';
            }
            return value[0] || '';
        }
        // 如果有 listValueMap,检查值是否为 label,需要反向映射为 value

#### frontend/i18n.js(7550 行,摘录前 120 行)

/**
 * i18n.js - 国际化模块(统一版)
 *
 * 架构说明:
 *   - 本模块是 i18n 的唯一权威来源
 *   - Pinia store (src/stores/app.js) 通过 initLangBridge() 桥接
 *   - 所有 Vue 组件通过 import { t } from 'frontend/i18n' 使用翻译
 *   - localStorage key 统一为 bossagents_language
 *
 * 支持语言:zh-CN, en-US, ja-JP, ko-KR
 */

import phosphorusDemoZh from '../src/i18n/phosphorus-demo-zh';
import { samsungDemoEn } from '../src/i18n/samsung-demo-en';

// ===== 常量 =====
const STORAGE_KEY = 'bossagents_language';
const SUPPORTED_LANGUAGES = [
    { code: 'zh-CN', name: '中文', shortName: '中文', flag: '🇨🇳' },
    { code: 'en-US', name: 'English', shortName: 'EN', flag: '🇺🇸' },
    { code: 'ja-JP', name: '日本語', shortName: '日本語', flag: '🇯🇵' },
    { code: 'ko-KR', name: '한국어', shortName: '한국어', flag: '🇰🇷' },
];

// ===== 内部语言状态(模块级) =====
let _currentLang = 'zh-CN';
let _onChangeCallbacks = [];

// Pinia bridge: store ref 注入
let _piniaRef = null;
export function bindPiniaLangRef(ref) {
    _piniaRef = ref;
    _currentLang = ref.value;
}

// ===== 语言检测 =====
function detectBrowserLang() {
    // 优先读 localStorage
    const stored = localStorage.getItem(STORAGE_KEY);
    if (stored && SUPPORTED_LANGUAGES.some(l => l.code === stored)) return stored;

    // 旧 key 兼容迁移
    const legacy = localStorage.getItem('bom-lang');
    if (legacy && SUPPORTED_LANGUAGES.some(l => l.code === legacy)) {
        localStorage.setItem(STORAGE_KEY, legacy);
        localStorage.removeItem('bom-lang');
        return legacy;
    }

    // 浏览器语言
    const browserLang = (navigator.language || navigator.userLanguage || 'en-US').toLowerCase();
    if (browserLang.startsWith('zh')) return 'zh-CN';
    if (browserLang.startsWith('ja')) return 'ja-JP';
    if (browserLang.startsWith('ko')) return 'ko-KR';
    return 'en-US';
}

// ===== 公开 API =====
export function getSupportedLanguages() { return SUPPORTED_LANGUAGES; }

export function getCurrentLanguage() { return _currentLang; }

export function setLanguage(langCode) {
    if (!SUPPORTED_LANGUAGES.some(l => l.code === langCode)) {
        console.warn(`[i18n] Unsupported language: ${langCode}`);
        return;
    }
    _currentLang = langCode;
    localStorage.setItem(STORAGE_KEY, langCode);
    if (_piniaRef) _piniaRef.value = langCode;
    _onChangeCallbacks.forEach(cb => cb(langCode));
    console.log(`🌐 Language switched to: ${langCode}`);
}

export function toggleLanguage() {
    const idx = SUPPORTED_LANGUAGES.findIndex(l => l.code === _currentLang);
    const next = SUPPORTED_LANGUAGES[(idx + 1) % SUPPORTED_LANGUAGES.length];
    setLanguage(next.code);
}

export function onLanguageChange(callback) {
    _onChangeCallbacks.push(callback);
    return () => { _onChangeCallbacks = _onChangeCallbacks.filter(cb => cb !== callback); };
}

// 向后兼容 proxy
export const currentLang = {
    get value() { return _currentLang; },
    set value(v) { setLanguage(v); }
};

// ===== 翻译字典 =====
export const i18nMessages = {
    'zh-CN': {
        // ── 登录 / 注册 ──
        login_title: '左帮右臂-老板私有智能体',
        login_subtitle: '左帮右臂 - BossPrive AI Agent',
        login_email: '邮箱',
        login_email_ph: '请输入邮箱',
        login_password: '密码',
        login_password_ph: '请输入密码',
        login_btn: '登 录',
        login_register_link: '注册新企业',
        login_connect: '连接到 SCSAI PLM',
        register_title: '注册新企业',
        register_enterprise_name: '企业名称',
        register_enterprise_name_ph: '输入企业名称',
        register_admin_email: '管理员邮箱',
        register_admin_email_ph: '输入管理员邮箱',
        register_password: '密码',
        register_password_ph: '设置管理员密码',
        register_code: '验证码',
        register_code_ph: '输入验证码',
        register_send_code: '发送验证码',
        register_btn: '注册并登录',
        register_login_link: '已有账号?去登录',

        // ── 状态 ──
        status_connected: '已连接至',
        status_not_connected: '未连接',

#### server/routes/aml.js(7126 行,摘录前 120 行)

/**
 * AML(对象类管理)路由模块
 *
 * 提供 AML 模板的 CRUD、验证、比对、修复、导入、统计、AI生成等 12 个 API。
 * 数据库层基于 better-sqlite3,自动建表。
 * SCSAI 连接工具用于从 SCSAI 获取真实数据(降级到本地)。
 */

const crypto = require('crypto');
const path = require('path');
const fs = require('fs');
const http = require('http');

// [P2] 模块化路由导入
const { handleItemTypeRoutes } = require('./itemtype.router');
const { handleImportExportRoutes } = require('./import-export.router');
const { handleAIAnalyzerRoutes } = require('./ai-analyzer.router');
const { handleMetaModelRoutes } = require('./meta-model.router');
const { handleBusinessSystemRoutes } = require('./business-system.router');
const { handleUnifiedAMLRoutes } = require('./unified-aml');
const { handleRuleEngine } = require('./rule-engine');
const { FeedbackAnalyzer } = require('../services/feedback-engine');
const { EvolutionScheduler } = require('../services/evolution-scheduler');
// rule-engine-core.js removed: prompt_templates table used instead
// 内联组装路由 (使用主模块的 getSciotDb)
async function handleAssembleRoutes(req, res, pathname, query, bodyStr) {
  const success = (data) => { res.writeHead(200, { 'Content-Type': 'application/json; charset=UTF-8' }); res.end(JSON.stringify({ success: true, data })); };
  const error = (msg, code = 400) => { res.writeHead(code, { 'Content-Type': 'application/json; charset=UTF-8' }); res.end(JSON.stringify({ success: false, error: msg })); };
  try {
    if (pathname === '/api/aml/assemble' && req.method === 'POST') {
      const body = JSON.parse(bodyStr || '{}');
      if (!body.llm_output) { error('缺少llm_output参数'); return true; }
      if (!body.item_type_name) { error('缺少item_type_name参数'); return true; }
      const sciotDb = getSciotDb();
      if (!sciotDb) { error('数据库不可用', 500); return true; }
      const assembler = require('../aml-assembler');
      const result = assembler.assembleAML(body.llm_output, body.item_type_name, sciotDb, body.options || {});
      success(result);
      return true;
    }
    if (pathname === '/api/aml/assemble/validate' && req.method === 'POST') {
      const body = JSON.parse(bodyStr || '{}');
      if (!body.aml) { error('缺少aml参数'); return true; }
      if (!body.item_type_name) { error('缺少item_type_name参数'); return true; }
      const sciotDb = getSciotDb();
      if (!sciotDb) { error('数据库不可用', 500); return true; }
      const assembler = require('../aml-assembler');
      const parsed = assembler.parseLLMOutput(body.aml);
      const tpl = sciotDb.prepare('SELECT * FROM sciot_templates WHERE item_type_name = ?').get(body.item_type_name);
      const merged = assembler.mergeWithTemplate(parsed, tpl || { item_type_name: body.item_type_name, required_fields: '[]', optional_fields: '[]' });
      merged.itemType = body.item_type_name;
      const validation = assembler.validateWithRules(merged, sciotDb);
      success({ valid: validation.valid, format: parsed.format, errors: validation.errors, warnings: validation.warnings, stats: { passed: validation.passed, failed: validation.failed } });
      return true;
    }
    const tplMatch = pathname.match(/^\/api\/aml\/assemble\/templates\/([^/]+)$/);
    if (tplMatch && req.method === 'GET') {
      const name = decodeURIComponent(tplMatch[1]);
      console.log('[assemble] GET templates for:', name);
      const sciotDb = getSciotDb();
      if (!sciotDb) { console.log('[assemble] no db'); error('数据库不可用', 500); return true; }
      console.log('[assemble] db OK');
      const tpl = sciotDb.prepare('SELECT * FROM sciot_templates WHERE item_type_name = ?').get(name);
      console.log('[assemble] tpl:', !!tpl);
      if (tpl) {
        success({ item_type_name: name, has_template: true, ...tpl,
          required_fields: JSON.parse(tpl.required_fields || '[]'),
          optional_fields: JSON.parse(tpl.optional_fields || '[]'),
          relationship_types: JSON.parse(tpl.relationship_types || '[]'),
        });
      } else {
        const props = sciotDb.prepare('SELECT name, label, data_type, is_required FROM sciot_properties WHERE item_type_name = ? ORDER BY name').all(name);
        success({ item_type_name: name, has_template: false, properties: props });
      }
      return true;
    }
  } catch(e) { error('组装错误: ' + e.message, 500); return true; }
  return false;
}

// [P0] 获取 Sciot 部件数据
async function handleSciotPartsRoutes(req, res, pathname, query, bodyStr) {
  const cachedBody = bodyStr || null;
  const success = (data) => { 
    res.writeHead(200, { 'Content-Type': 'application/json; charset=UTF-8' }); 
    res.end(JSON.stringify(data)); 
  };
  const error = (msg, code = 400) => { 
    res.writeHead(code, { 'Content-Type': 'application/json; charset=UTF-8' }); 
    res.end(JSON.stringify({ success: false, error: msg })); 
  };
  
  try {
    if (pathname === '/api/sciot/parts' && req.method === 'GET') {
      const sciotDb = getSciotDb();
      if (!sciotDb) return error('数据库不可用', 500);
      
      let parts = [];
      try {
        parts = sciotDb.prepare('SELECT * FROM sciot_parts LIMIT 100').all();
      } catch (e) {
        console.warn('[AML] sciot_parts 表不存在,返回模拟数据');
        parts = [
          { id: 'P001', item_number: '1000-001', name: '主控制器', classification: 'Electronic', state: 'Released', created_on: '2024-01-01' },
          { id: 'P002', item_number: '2000-042', name: '电源模块', classification: 'Power', state: 'In Work', created_on: '2024-02-15' },
          { id: 'P003', item_number: '3000-115', name: '传感器 A', classification: 'Sensor', state: 'Released', created_on: '2024-03-20' }
        ];
      }
      return success({ success: true, data: parts });
    }
  } catch (e) {
    return error('获取部件错误: ' + e.message, 500);
  }
  return false;
}

// ========== 使用统一数据库 ==========

let db = null;
try {

#### src/views/StaffCapabilities.vue(4986 行,摘录前 120 行)

<template>
  <div class="staff-capabilities">
    <!-- 页面标题 -->
    <div class="page-header">
      <h1 class="page-title">🤖 数字员工能力中心</h1>
      <p class="page-subtitle">基于大模型的通用业务对象处理平台</p>
      <!-- 切换开关:使用新 CapabilityRuntime API -->
      <div class="api-switch" style="margin-top:8px;display:flex;align-items:center;gap:8px">
        <span style="font-size:12px;color:#6b7280">使用新 API:</span>
        <label style="position:relative;display:inline-block;width:40px;height:20px;cursor:pointer">
          <input type="checkbox" v-model="useNewAPI" style="opacity:0;width:0;height:0" />
          <span style="position:absolute;top:0;left:0;right:0;bottom:0;background-color:#ccc;border-radius:20px;transition:.3s" :style="useNewAPI ? {backgroundColor:'#10b981'} : {}">
            <span style="position:absolute;content:'';height:16px;width:16px;left:2px;bottom:2px;background-color:white;border-radius:50%;transition:.3s" :style="useNewAPI ? {transform:'translateX(20px)'} : {}"></span>
          </span>
        </label>
        <span style="font-size:12px;color:#6b7280">{{ useNewAPI ? '开启(新 API)' : '关闭(旧实现)' }}</span>
      </div>
    </div>

    <!-- 能力选择标签 -->
    <div class="capability-tabs">
      <button 
        v-for="cap in allCapabilities" 
        :key="cap.id"
        class="tab-btn"
        :class="{ 
          active: activeCapability === cap.id,
          disabled: !currentObjectCapabilities.includes(cap.id)
        }"
        @click="currentObjectCapabilities.includes(cap.id) && setCapability(cap.id)"
      >
        <span class="tab-icon">{{ cap.icon }}</span>
        <span class="tab-label">{{ cap.label }}</span>
        <span v-if="!currentObjectCapabilities.includes(cap.id) && selectedItemType" class="tab-lock">🚫</span>
      </button>
    </div>

    <!-- 主内容区 -->
    <div class="main-content">
      <!-- 左侧:对象选择 -->
      <div class="object-panel">
        <div class="panel-header">
          <h3>📋 业务对象</h3>
          <span class="object-count">{{ filteredObjects.length }} 个</span>
        </div>
        <!-- 分类标签 -->
        <div class="category-tabs">
          <button 
            v-for="cat in categories" 
            :key="cat.key"
            class="cat-btn"
            :class="{ active: activeCategory === cat.key }"
            :style="activeCategory === cat.key ? { borderColor: cat.color, color: cat.color } : {}"
            @click="activeCategory = cat.key"
          >
            <span class="cat-icon">{{ cat.icon }}</span>
            <span class="cat-label">{{ cat.label }}</span>
          </button>
        </div>

        <!-- 对象卡片网格 -->
        <div class="object-cards">
          <div 
            v-for="obj in filteredObjects" 
            :key="obj.name"
            class="object-card"
            :class="{ active: selectedItemType === obj.name }"
            @click="selectObjectType(obj.name)"
          >
            <div class="card-header">
              <span class="card-cat-icon" :style="{ color: OBJECT_CATEGORIES[obj.category]?.color || '#6b7280' }">
                {{ OBJECT_CATEGORIES[obj.category]?.icon || '📁' }}
              </span>
              <span class="card-name">{{ obj.label }}</span>
              <span v-if="obj.isCore" class="core-badge">核心</span>
            </div>
            <div class="card-desc">{{ obj.description }}</div>
            <div class="card-caps">
              <span 
                v-for="cap in allCapabilities" 
                :key="cap.id"
                class="cap-dot"
                :class="{ supported: obj.capabilities.includes(cap.id) }"
                :title="cap.label"
              >{{ cap.icon }}</span>
            </div>
          </div>
        </div>
      </div>
      <!-- 右侧:操作面板 -->
      <div class="action-panel">
        <!-- 对象数据选择区(选择对象类型后出现) -->
        <div v-if="selectedItemType" class="data-selection-bar">
          <div class="data-bar-left">
            <span class="data-bar-label">当前对象:</span>
            <span class="data-bar-type">{{ currentObjectConfig?.label }}</span>
            <span v-if="selectedItem" class="data-bar-item">
              {{ getFieldValue(selectedItem, 'code') }} - {{ getFieldValue(selectedItem, 'name') }}
            </span>
            <span v-else class="data-bar-hint">请从下方列表选择一个实例</span>
          </div>
          <div class="data-bar-right">
            <input 
              v-model="searchQuery" 
              type="text" 
              class="data-search-input"
              placeholder="搜索..."
              @keyup.enter="searchObjects"
            />
            <button class="btn-search-sm" @click="searchObjects">🔍</button>
          </div>
        </div>

        <!-- 实例列表(可折叠) -->
        <div v-if="selectedItemType && showInstanceList" class="instance-list-panel">
          <div class="instance-list-header" @click="showInstanceList = !showInstanceList">
            <span>📋 实例列表 ({{ objectList.length }})</span>
            <span class="toggle-icon">{{ showInstanceList ? '▲' : '▼' }}</span>

#### src/views/RulesAndTemplates.vue(3514 行,摘录前 120 行)

<template>
  <div class="page-container">
    <div class="page-header">
      <h1 class="page-title">📐 规则与模板</h1>
      <p class="page-subtitle">规则引擎、提示词管理与模板库</p>
    </div>

    <div class="section-tabs">
      <button
        v-for="tab in tabs"
        :key="tab.key"
        class="section-tab"
        :class="{ active: activeTab === tab.key }"
        @click="activeTab = tab.key"
      >
        {{ tab.label }}
      </button>
    </div>

    <div v-if="loading" class="card" style="padding:40px;text-align:center;color:var(--text-secondary);">加载中...</div>
    <div v-else-if="error" class="card" style="padding:40px;text-align:center;color:var(--error);">{{ error }}</div>

    <!-- ===== 系统总览 ===== -->
    <div class="card" v-if="!loading && !error && activeTab === 'system-overview'">
      <div class="card-header">
        <h3 class="card-title">📊 系统总览</h3>
        <div class="header-actions">
          <input v-model="sysSearch" class="form-input-sm" placeholder="搜索对象类/业务域..." style="max-width:220px;" />
          <button class="btn btn-sm btn-secondary" @click="loadSystemStatus()" :disabled="sysStatusLoading">{{ sysStatusLoading ? '加载中...' : '🔄 刷新' }}</button>
        </div>
      </div>
      <div class="card-body" v-if="sysStatus">
        <!-- 数据统计 -->
        <div class="overview-stats" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:8px;margin-bottom:12px;">
          <div class="stat-card" style="background:#eef2ff;padding:8px;border-radius:6px;text-align:center;">
            <div style="font-size:22px;font-weight:700;color:#2563eb;">{{ sysStatus.summary?.totalItemTypes || 0 }}</div>
            <div style="font-size:11px;color:#666;">SCSAI对象类</div>
          </div>
          <div class="stat-card" style="background:#ecfdf5;padding:8px;border-radius:6px;text-align:center;">
            <div style="font-size:22px;font-weight:700;color:#059669;">{{ sysStatus.summary?.templates || 0 }}</div>
            <div style="font-size:11px;color:#666;">已配模板</div>
          </div>
          <div class="stat-card" style="background:#fffbeb;padding:8px;border-radius:6px;text-align:center;">
            <div style="font-size:22px;font-weight:700;color:#d97706;">{{ sysStatus.summary?.rules || 0 }}</div>
            <div style="font-size:11px;color:#666;">校验规则</div>
          </div>
          <div class="stat-card" style="background:#fef2f2;padding:8px;border-radius:6px;text-align:center;">
            <div style="font-size:22px;font-weight:700;color:#dc2626;">{{ sysStatus.summary?.prompts?.total || 0 }}</div>
            <div style="font-size:11px;color:#666;">提示词</div>
          </div>
          <div class="stat-card" style="background:#f0fdfa;padding:8px;border-radius:6px;text-align:center;" @click="sysSelectedType=null">
            <div style="font-size:22px;font-weight:700;color:#0d9488;">{{ sysStatus.coverage?.fullCoveredPct || 0 }}%</div>
            <div style="font-size:11px;color:#666;">完整覆盖率</div>
          </div>
        </div>

        <!-- 主区域:双栏布局(左:列表,右:详情) -->
        <div :style="{ display: 'grid', gridTemplateColumns: sysSelectedType ? '1fr 1.5fr' : '1fr', gap: '16px' }">
          <!-- 左侧:覆盖情况表格 -->
          <div>
            <h4 style="margin-bottom:6px;font-size:14px;">
              对象类覆盖情况
              <span class="text-muted" style="font-weight:400;font-size:12px;">(点击行查看详情)</span>
            </h4>
            <div style="max-height:500px;overflow-y:auto;border:1px solid #e0e0e0;border-radius:4px;">
              <table class="table table-striped" style="font-size:12px;">
                <thead><tr>
                  <th>对象类</th>
                  <th style="width:55px;">模板</th>
                  <th style="width:48px;">LLM</th>
                  <th style="width:48px;">自动</th>
                  <th style="width:55px;">规则</th>
                  <th style="width:55px;">提示词</th>
                  <th style="width:48px;">状态</th>
                </tr></thead>
                <tbody>
                  <tr v-for="c in filteredSysDetails" :key="c.name"
                    @click="sysSelectedType = (sysSelectedType?.name === c.name ? null : c)"
                    :class="{ active: sysSelectedType?.name === c.name }"
                    style="cursor:pointer;">
                    <td><span class="text-mono">{{ c.name }}</span></td>
                    <td><span :style="{color:c.hasTemplate?'#059669':'#dc2626'}">{{ c.hasTemplate ? '✔' : '✘' }}</span></td>
                    <td>{{ c.tmplInfo?.llm ?? '-' }}</td>
                    <td>{{ c.tmplInfo?.auto ?? '-' }}</td>
                    <td>{{ c.ruleCount }}</td>
                    <td>
                      <span v-if="c.hasPrompts" style="color:#059669;">✔</span>
                      <span v-else style="color:#dc2626;">✘</span>
                    </td>
                    <td>
                      <span v-if="c.hasTemplate && c.hasRules && c.hasPrompts" class="badge badge-success" style="font-size:10px;">完整</span>
                      <span v-else-if="c.hasTemplate" class="badge badge-warning" style="font-size:10px;">部分</span>
                      <span v-else class="badge badge-secondary" style="font-size:10px;">未配</span>
                    </td>
                  </tr>
                </tbody>
              </table>
            </div>
          </div>

          <!-- 右侧:选中类型的详情面板 -->
          <div v-if="sysSelectedType" style="border:1px solid #d0d0d0;border-radius:6px;padding:12px;overflow-y:auto;max-height:600px;">
            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;padding-bottom:8px;border-bottom:2px solid #2563eb;">
              <h3 style="margin:0;font-size:16px;">{{ sysSelectedType.name }}</h3>
              <span class="badge" :class="sysSelectedType.hasTemplate&&sysSelectedType.hasRules&&sysSelectedType.hasPrompts?'badge-success':'badge-warning'">
                {{ sysSelectedType.hasTemplate&&sysSelectedType.hasRules&&sysSelectedType.hasPrompts?'已配齐':'未配齐' }}
              </span>
            </div>

            <!-- 模板字段 -->
            <div style="margin-bottom:12px;">
              <h4 style="font-size:13px;margin-bottom:4px;display:flex;gap:8px;">
                <span>📄 模板字段</span>
                <span v-if="sysSelectedType.tmplInfo" style="font-weight:400;">
                  (LLM:{{ sysSelectedType.tmplInfo.llm }} 自动:{{ sysSelectedType.tmplInfo.auto }} 必填:{{ sysSelectedType.tmplInfo.required }})
                </span>
                <span v-else class="text-muted" style="font-weight:400;">(未配置)</span>
              </h4>
              <div v-if="sysSelectedType.tmplInfo" style="display:flex;gap:6px;flex-wrap:wrap;font-size:12px;">
                <span class="tag tag-llm" v-for="i in sysSelectedType.tmplInfo.llm" :key="'l'+i" style="cursor:help;" :title="'LLM生成字段'">🤖 {{ getFieldName(sysSelectedType.name, i) || i }}</span>

#### server/core/rule-engine.js(3491 行,摘录前 120 行)

/**
 * 统一规则引擎 (Unified Rule Engine) v3.0
 * 
 * 功能:驱动对象识别、创建、修复、优化、比对等所有业务功能
 * 特点:
 * 1. 规则存储在数据库,可管理优化
 * 2. 支持从AML文件生成规则
 * 3. 支持从SCSAI原生获取规则
 * 4. 支持手工维护规则
 * 5. 多级缓存保证性能
 * 6. 规则工厂模式:从sciot_properties自动生成所有ItemType规则(全覆盖)
 */

// 容错 JSON 解析:tags 字段可能是 JSON 数组字符串或逗号分隔文本
function _safeJsonParse(val, fallback = []) {
  if (!val) return fallback;
  try { return JSON.parse(val); }
  catch { return String(val).split(',').filter(Boolean); }
}

function _safeParseJson(val, fallback = {}) {
  if (val === null || val === undefined || val === '') return fallback;
  if (typeof val !== 'string') return val;
  try {
    const parsed = JSON.parse(val);
    // 双重编码兼容:DB 中 condition/action_config 被 JSON.stringify 两次,
    // 解析后仍是字符串则再解一层,确保得到真正的对象
    if (typeof parsed === 'string') {
      try { return JSON.parse(parsed); } catch { return parsed; }
    }
    return parsed;
  } catch { return fallback; }
}

const path = require('path');
const fs = require('fs');
const vm = require('vm');

// 安全的脚本执行上下文 - 限制全局访问
function createSafeContext() {
  return {
    console: { log: () => {}, error: () => {}, warn: () => {} },
    Math: Math,
    Date: Date,
    JSON: JSON,
    Array: Array,
    Object: Object,
    String: String,
    Number: Number,
    Boolean: Boolean,
    parseInt: parseInt,
    parseFloat: parseFloat,
    isNaN: isNaN,
    isFinite: isFinite,
    encodeURI: encodeURI,
    encodeURIComponent: encodeURIComponent,
    decodeURI: decodeURI,
    decodeURIComponent: decodeURIComponent,
    escape: escape,
    unescape: unescape,
    undefined: undefined,
    NaN: NaN,
    Infinity: Infinity,
  };
}

// 安全执行用户脚本
function runSafeScript(script, args, logger) {
  try {
    const context = vm.createContext(createSafeContext());
    const fn = new vm.Script('(' + script + ')').runInContext(context);
    if (typeof fn !== 'function') {
      throw new Error('脚本必须返回一个函数');
    }
    return fn.apply(null, args);
  } catch (e) {
    if (logger) logger.error('[RuleEngine] 脚本执行失败:', e);
    throw e;
  }
}

// ==================== 规则缓存管理 ====================
class RuleCache {
  constructor(options = {}) {
    this.ttl = options.ttl || 5 * 60 * 1000;
    this.maxSize = options.maxSize || 1000;
    this.cache = new Map();
    this.stats = { hits: 0, misses: 0, evictions: 0 };
  }

  get(key) {
    const item = this.cache.get(key);
    if (!item) { this.stats.misses++; return null; }
    if (Date.now() > item.expiry) { this.cache.delete(key); this.stats.evictions++; return null; }
    this.stats.hits++;
    item.lastAccess = Date.now();
    return item.value;
  }

  set(key, value, ttl = this.ttl) {
    if (this.cache.size >= this.maxSize) this._evictLRU();
    this.cache.set(key, { value, expiry: Date.now() + ttl, lastAccess: Date.now() });
  }

  invalidate(key) { this.cache.delete(key); }

  invalidatePattern(pattern) {
    for (const key of this.cache.keys()) { if (pattern.test(key)) this.cache.delete(key); }
  }

  clear() { this.cache.clear(); this.stats = { hits: 0, misses: 0, evictions: 0 }; }

  _evictLRU() {
    let oldest = null, oldestKey = null;
    for (const [key, item] of this.cache) {
      if (!oldest || item.lastAccess < oldest) { oldest = item.lastAccess; oldestKey = key; }
    }
    if (oldestKey) { this.cache.delete(oldestKey); this.stats.evictions++; }
  }

#### src/views/DigitalStaff.vue(3262 行,摘录前 120 行)

<template>
  <div class="digital-staff">
    <div class="page-header">
      <h1 class="page-title">{{ t('staff_title') }}</h1>
      <p class="page-subtitle">{{ t('staff_subtitle') }}</p>
      <button class="btn btn-primary btn-sm" @click="openCreateStaff">{{ t('staff_create') }}</button>
    </div>

    <div class="tab-bar">
      <button class="tab" :class="{ active: activeTab === 'staff' }" @click="activeTab = 'staff'">{{ t('staff_tab_list') }}</button>
      <button class="tab" :class="{ active: activeTab === 'board' }" @click="activeTab = 'board'; fetchTasks()">{{ t('staff_tab_board') }}</button>
    </div>

    <!-- ============ 员工列表标签 ============ -->
    <template v-if="activeTab === 'staff'">
      <div v-if="loading" class="loading-state">{{ t('staff_loading') }}</div>
      <div v-else-if="error && !isDemo" class="error-state">{{ error }}</div>
      <template v-else>
        <div class="staff-grid">
          <div v-for="staff in staffList" :key="staff.id" class="staff-card" :class="{ active: selectedStaff?.id === staff.id }" @click="selectStaff(staff)">
            <div class="staff-avatar">{{ staff.avatar }}</div>
            <div class="staff-info">
              <div class="staff-name">{{ getStaffNameLabel(staff) }}</div>
              <div class="staff-role">{{ staff.role }}</div>
              <div class="staff-status">
                <span class="status-dot" :class="staff.status"></span>
                <span>{{ staff.statusText }}</span>
              </div>
            </div>
            <div class="staff-metrics">
              <div class="metric">
                <div class="metric-value">{{ staff.tasksCompleted }}</div>
                <div class="metric-label">{{ t('staff_runs') }}</div>
              </div>
            </div>
            <div class="staff-actions">
              <button class="btn btn-sm btn-outline btn-ability" @click.stop="showAbility(staff)" :title="t('staff_ability')">📖</button>
              <button class="btn btn-sm btn-outline" @click.stop="showPortrait(staff)" title="能力画像">🧩</button>
              <button class="btn btn-sm btn-primary" @click.stop="openSummon(staff)" :disabled="staff.isRunning" :title="staff.isRunning ? t('staff_running_title') : t('staff_run')">
                {{ staff.isRunning ? t('staff_running') : t('staff_run') }}
              </button>
            </div>
          </div>
        </div>

        <div v-if="selectedStaff" class="staff-detail">
          <div class="card">
            <div class="card-header">
              <div class="card-title">
                <span>{{ selectedStaff.avatar }}</span>
                <span>{{ getStaffNameLabel(selectedStaff) }}</span>
              </div>
              <div class="card-actions">
                <button class="btn btn-sm btn-outline" @click="showAbility(selectedStaff)" :title="t('staff_ability')">📖 {{ t('staff_ability') }}</button>
                <button class="btn btn-sm btn-outline" @click="openEditStaff(selectedStaff)">{{ t('staff_edit') }}</button>
                <button class="btn btn-sm btn-outline" @click="loadVersions(selectedStaff.id)">{{ t('staff_versions') }}</button>
                <button class="btn btn-sm btn-danger" @click="deleteStaff(selectedStaff)">{{ t('staff_delete') }}</button>
                <label class="toggle-switch">
                  <input type="checkbox" :checked="selectedStaff._enabled" @change="toggleStaffEnabled(selectedStaff)" />
                  <span class="toggle-slider"></span>
                </label>
                <span class="toggle-label">{{ selectedStaff._enabled ? t('staff_enabled') : t('staff_disabled') }}</span>
              </div>
            </div>
            <div class="detail-content">
              <div class="detail-section">
                <h3>{{ t('staff_basic_info') }}</h3>
                <div class="info-grid">
                  <div class="info-item"><span class="info-label">{{ t('staff_role') }}</span><span class="info-value">{{ selectedStaff.role }}</span></div>
                  <div class="info-item"><span class="info-label">{{ t('staff_level') }}</span><span class="info-value">{{ selectedStaff.level }}</span></div>
                  <div class="info-item"><span class="info-label">{{ t('staff_department') }}</span><span class="info-value">{{ selectedStaff.department }}</span></div>
                  <div class="info-item"><span class="info-label">{{ t('staff_expertise') }}</span><span class="info-value">{{ selectedStaff.expertise }}</span></div>
                  <div class="info-item"><span class="info-label">{{ t('staff_work_status') }}</span><span class="info-value"><span class="status-dot" :class="selectedStaff.status"></span>{{ selectedStaff.statusText }}</span></div>
                  <div class="info-item"><span class="info-label">{{ t('staff_schedule') }}</span><span class="info-value"><code>{{ selectedStaff.schedule || t('staff_not_set') }}</code></span></div>
                </div>
              </div>
              <div class="detail-section">
                <h3>{{ t('staff_execution') }}</h3>
                <div class="info-grid">
                  <div class="info-item"><span class="info-label">{{ t('staff_total_executions') }}</span><span class="info-value">{{ selectedStaff.tasksCompleted }} {{ t('staff_runs') }}</span></div>
                  <div class="info-item"><span class="info-label">{{ t('staff_last_run') }}</span><span class="info-value">{{ selectedStaff.lastRunAt || t('staff_no_record') }}</span></div>
                  <div class="info-item"><span class="info-label">{{ t('staff_last_result') }}</span><span class="info-value">{{ selectedStaff.lastRunResult || t('staff_no_record') }}</span></div>
                  <!-- ★ Task C: 总资产贡献 -->
                  <div class="info-item" v-if="staffContributionTotal.qualityTotal !== 0 || staffContributionTotal.valueTotal !== 0">
                    <span class="info-label">{{ t('staff_asset_contribution') || '资产贡献' }}</span>
                    <span class="info-value" style="color:#2e7d32;font-weight:600;">
                      {{ staffContributionTotal.qualityTotal > 0 ? `+${staffContributionTotal.qualityTotal}分质量` : '' }}{{ staffContributionTotal.qualityTotal !== 0 && staffContributionTotal.valueTotal !== 0 ? ' / ' : '' }}{{ staffContributionTotal.valueTotal > 0 ? `+¥${staffContributionTotal.valueTotal}` : '' }}
                    </span>
                  </div>
                </div>
                <div style="margin-top: 8px;">
                  <button class="btn btn-sm btn-primary" @click="openSummon(selectedStaff)" :disabled="selectedStaff.isRunning">{{ selectedStaff.isRunning ? t('staff_summon_running') : t('staff_summon') }}</button>
                </div>
              </div>
              <div class="detail-section">
                <h3>{{ t('staff_skills') }}</h3>
                <div class="skills-list">
                  <span v-for="skill in selectedStaff.skills" :key="skill" class="skill-tag">{{ skill }}</span>
                </div>
              </div>
              <div class="detail-section">
                <h3>{{ t('staff_recent_tasks') }}</h3>
                <div class="task-list">
                  <div v-for="task in selectedStaff.recentTasks" :key="task.id" class="task-item">
                    <div class="task-status" :class="task.status"></div>
                    <div class="task-info">
                      <div class="task-title">{{ task.title }}</div>
                      <div class="task-time">{{ task.time }}</div>
                    </div>
                  </div>
                </div>
              </div>

              <!-- 结果与报告面板 -->
              <div class="detail-section">
                <div class="section-header">
                  <h3>{{ t('staff_reports') }}</h3>
                  <div v-if="staffReports.length > 0" class="log-filter-bar">
                    <span style="font-size:12px;color:var(--text-muted);">{{ t('staff_reports_count', { count: staffReports.length }) }}</span>
                  </div>

#### src/views/AiWorkbench.vue(3260 行,摘录前 120 行)

<template>
  <div class="ai-workbench">
    <!-- 页面标题 -->
    <div class="page-header">
      <h1 class="page-title" style="font-size:18px">{{ t('ai_title') }}</h1>
    </div>

    <div class="workbench-layout">
      <!-- ★ 会话列表侧边栏 -->
      <div class="session-sidebar">
        <div class="sidebar-header">
          <span class="sidebar-title">{{ t('ai_sessions') }}</span>
          <span class="sidebar-count">{{ groupedSessions.length }}</span>
        </div>
        <div class="sidebar-list">
          <div 
            v-for="session in groupedSessions" 
            :key="session.id"
            class="session-item"
            :class="{ active: currentSessionId === session.id }"
            @click="currentSessionId = session.id"
          >
            <div class="session-item-header">
              <span class="session-icon">{{ session.source === 'feishu' ? '💬' : '🌐' }}</span>
              <span class="session-label">{{ session.label }}</span>
            </div>
            <div class="session-item-meta">
              <span class="session-count">{{ session.messages.length }} {{ t('ai_msgs') }}</span>
              <span class="session-time">{{ session.timeLabel }}</span>
            </div>
          </div>
          <div v-if="groupedSessions.length === 0" class="sidebar-empty">
            {{ t('ai_sessions_empty') }}
          </div>
        </div>
      </div>

      <!-- 主聊天区域 -->
      <div class="chat-area">
        <div class="card chat-card">
          <!-- Tab 切换 -->
          <div class="tab-bar">
            <button 
              class="tab-btn" 
              :class="{ active: activeTab === 'chat' }"
              @click="activeTab = 'chat'"
            >
              {{ t('ai_tab_chat') }}
            </button>
            <button 
              class="tab-btn" 
              :class="{ active: activeTab === 'logs' }"
              @click="activeTab = 'logs'"
            >
              {{ t('ai_tab_logs') }}
            </button>
            <button 
              class="tab-btn" 
              :class="{ active: activeTab === 'skill' }"
              @click="activeTab = 'skill'"
            >
              🎯 体验Skill
            </button>
            <button 
              v-if="isDemo"
              class="tab-btn" 
              :class="{ active: activeTab === 'integration' }"
              @click="activeTab = 'integration'"
            >
              🔗 Integration
            </button>
          </div>

          <!-- ========== 对话 Tab ========== -->
          <div v-show="activeTab === 'chat'" class="tab-content">
          <!-- 聊天头部 -->
          <div class="chat-header">
            <div class="chat-title">
              <span class="ai-icon">🤖</span>
              <span>{{ t('ai_ai_assistant') }}</span>
            </div>
            <div class="chat-header-actions">
              <span class="staff-count">{{ staffOnline }} {{ t('ai_staff_online') }}</span>
              <button class="btn btn-sm btn-secondary" @click="clearChat">{{ t('ai_clear') }}</button>
            </div>
          </div>

          <!-- ★ 快捷员工工具栏(一键唤起) -->
          <div class="quick-staff-bar" v-if="staffList.length > 0">
            <div class="quick-staff-scroll">
              <button
                v-for="s in staffList"
                :key="s.id"
                class="quick-staff-btn"
                :title="s.name + ': ' + (s.description || s.worker || '')"
                @click="summonStaffFromChat(s)"
              >
                <span class="qs-avatar">{{ getStaffIcon(s.id) }}</span>
                <span class="qs-name">{{ displayName(s.name) }}</span>
              </button>
            </div>
          </div>

          <!-- ★ 员工召唤内联参数面板(替代 prompt()) -->
          <div class="summon-panel" v-if="summonPanel.open">
            <div class="summon-header">
              <span class="summon-icon">{{ summonPanel.banner?.icon || '🤖' }}</span>
              <span class="summon-title">{{ summonPanel.banner?.title || summonPanel.staff.name }}</span>
              <button class="summon-close" @click="closeSummonPanel">&times;</button>
            </div>
            <p class="summon-desc" v-if="summonPanel.banner?.desc">{{ summonPanel.banner.desc }}</p>
            <!-- 步骤 -->
            <div class="summon-steps" v-if="summonPanel.banner?.steps?.length">
              <span v-for="(step, i) in summonPanel.banner.steps" :key="i" class="summon-step">{{ step }}</span>
            </div>
            <!-- 意图输入 -->
            <input class="summon-intent" v-model="summonPanel.intent" :placeholder="summonPanel.banner?.placeholder || t('ai_summon_placeholder')" @keyup.enter="executeSummonPanel" />
            <!-- 动态参数表单 -->
            <div class="summon-params" v-if="summonPanel.banner?.params?.length">
              <div v-for="p in summonPanel.banner.params" :key="p.key" class="summon-param">

#### server/ai-agent.js(3098 行,摘录前 120 行)

/**
 * ========================================
 *        AI 操作代理核心模块
 * ========================================
 *
 * 用户用自然语言描述意图,LLM 理解后通过 function calling 调用系统 API 执行操作。
 *
 * 核心流程:
 *   用户说:"给所有对象类加一个 name 属性"
 *     → LLM 理解意图
 *     → 调用 optimize_item_types 工具
 *     → 执行数据库操作
 *     → 返回结果
 */

const LLMBrain = require('./digital-staff/llm-brain');
const SCSAITools = require('./utils/SCSAI-tools');
const alibaba1688 = require('./services/alibaba-1688-service');
const { SCSAIClient } = require('./utils/SCSAI-client');

// ======================================================
// 意图识别引擎(v2)—— 规则从 DB 加载,代替硬编码关键词
// ======================================================
const { getIntentEngine } = require('./routes/intent');
let _intentEngine = null;
function getIntentEngineInstance(db) {
    if (!_intentEngine) {
        // 首次调用时懒初始化(db 可能还未就绪)
        _intentEngine = getIntentEngine(db || null);
    }
    return _intentEngine;
}

// ======================================================
// 异步工作流状态存储(支持前端轮询)
// ======================================================
const _workflowStates = new Map(); // workflowId -> { status, steps, result, createdAt, updatedAt }
function getWorkflowState(id) { return _workflowStates.get(id) || null; }
function setWorkflowState(id, state) { _workflowStates.set(id, { ...state, updatedAt: Date.now() }); }
// 清理超过2小时的旧数据
setInterval(() => {
    const now = Date.now();
    for (const [id, s] of _workflowStates) {
        if (now - (s.createdAt || 0) > 7200000) _workflowStates.delete(id);
    }
}, 600000);

// SCSAI 客户端(从环境变量读取配置,与 digital-staff/index.js 一致)
let _SCSAIClient = null;
function getSCSAIClient() {
    if (!_SCSAIClient) {
        _SCSAIClient = new SCSAIClient({
            serverUrl: process.env.SCSAI_SERVER,
            database: process.env.SCSAI_DATABASE || 'SCPLM',
            username: process.env.SCSAI_USERNAME,
            password: process.env.SCSAI_PASSWORD,
        });
    }
    return _SCSAIClient;
}

// LLMBrain 单例缓存,避免多次实例化带来的副作用
function getBrain() {
    return LLMBrain.getGlobalInstance();
}
function resetBrain() {
    LLMBrain.resetGlobalInstance();
    console.log('[AI-Agent] LLMBrain 全局单例已重置,下次调用将重新加载配置');
}

// ========== CapabilityRuntime 集成 ==========
const CapabilityRuntime = require('./core/capability-runtime');
let _capabilityRuntime = null;
function getCapabilityRuntime() {
    if (!_capabilityRuntime) {
        const client = getSCSAIClient();
        _capabilityRuntime = new CapabilityRuntime({
            SCSAIClient: client,
            // 关键:挂载 applyAMLFn,规则引擎通过它提交 AML 到 SCSAI
            applyAML: async (aml) => {
                const result = await client.sendAML(aml);
                return result;
            },
            debug: true
        });
    }
    return _capabilityRuntime;
}

/**
 * 执行能力调用
 * @param {Object} step - { capability, item_type, params }
 */
async function executeCapabilityCall(step) {
    const { capability, item_type, params = {} } = step;
    const runtime = getCapabilityRuntime();
    
    console.log(`[AI-Agent] 执行能力: ${capability}(${item_type})`, params);
    
    try {
        const context = { item_type, ...params };
        const result = await runtime[capability](context);
        
        return {
            success: result.success !== false,
            result,
            message: result.message || `${capability} ${item_type} 完成`
        };
    } catch (e) {
        console.error(`[AI-Agent] 能力执行失败:`, e.message);
        return {
            success: false,
            error: e.message,
            message: sharedI18n.T('agent_op_failed') + ': ' + e.message
        };
    }
}

/**
 * 执行多步骤计划

#### src/views/ItemTypeManagement.vue(2955 行,摘录前 120 行)

<template>
  <div class="itm-page">
    <div class="page-header">
      <h1 class="page-title">🧬 {{ t('item_type_title') }}</h1>
      <p class="page-subtitle">{{ t('item_type_subtitle') }}</p>
    </div>
    <!-- ==================== 左侧面板:对象类列表 ==================== -->
    <!-- ==================== 视图模式切换 Tab ==================== -->
    <div class="itm-view-tabs">
      <button class="itm-view-tab" :class="{ active: viewMode === 'business' }" @click="viewMode = 'business'">
        &#x2B50; {{ t('item_type_business') }}
      </button>
      <button class="itm-view-tab" :class="{ active: viewMode === 'system' }" @click="viewMode = 'system'">
        &#x2699; {{ t('item_type_system') }}
      </button>
      <button class="itm-view-tab" :class="{ active: viewMode === 'all' }" @click="viewMode = 'all'">
        &#x1F4CB; 全部
      </button>
    </div>

    <div class="itm-body">
    <div class="itm-left-panel">
      <!-- 工具栏 -->
      <div class="itm-toolbar">
        <button
          class="btn btn-primary btn-sm"
          :disabled="itemtypeLoading"
          @click="showCreateDialog = true"
        >
          <span class="btn-icon">✨</span>
          创建业务对象
        </button>
        <button
          class="btn btn-primary btn-sm"
          :disabled="isInspecting || itemtypeLoading"
          @click="handleRunInspection"
        >
          <span v-if="isInspecting" class="btn-spinner"></span>
          <span v-else class="btn-icon">&#x1F916;</span>
          AI巡检
        </button>
        <button
          class="btn btn-secondary btn-sm"
          :disabled="itemtypeLoading"
          @click="loadItemTypeList(true)"
        >
          <span class="btn-icon">&#x1F504;</span>
          刷新
        </button>
        <button
          class="btn btn-secondary btn-sm"
          :disabled="batchLoading"
          @click="handleBatchOptimize"
        >
          <span v-if="batchLoading" class="btn-spinner"></span>
          <span v-else class="btn-icon">&#x26A1;</span>
          批量优化
        </button>
        <button
          class="btn btn-secondary btn-sm"
          :disabled="patrolLoading"
          @click="handlePatrol"
        >
          <span v-if="patrolLoading" class="btn-spinner"></span>
          <span v-else class="btn-icon">&#x1F50D;</span>
          全库巡检
        </button>
      </div>

      <!-- 巡检结果摘要 -->
      <div v-if="patrolResult" style="padding:12px;background:var(--card-bg);border:1px solid var(--border-color);border-radius:6px;margin-bottom:12px;font-size:13px;">
        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
          <strong style="font-size:14px;">🔍 巡检结果</strong>
          <button class="btn btn-sm" style="padding:2px 8px;font-size:11px;" @click="patrolResult=null">关闭</button>
        </div>
        <div style="display:flex;gap:16px;margin-bottom:8px;">
          <span>扫描类型: <strong>{{ patrolResult.totalScanned || patrolResult.totalTypes || 0 }}</strong></span>
          <span>健康分布:</span>
          <span v-for="g in ['A','B','C','D','F']" :key="g" :style="{color: g==='A'?'#22c55e':g==='B'?'#3b82f6':g==='C'?'#f59e0b':g==='D'?'#f97316':'#ef4444'}">
            {{ g }}: {{ (patrolResult.gradeDistribution || patrolResult.distribution)?.[g] || 0 }}
          </span>
        </div>
        <div v-if="patrolResult.lowHealthTypes?.length" style="max-height:150px;overflow-y:auto;">
          <div style="font-weight:600;color:#f59e0b;margin-bottom:4px;">⚠️ 低分对象类:</div>
          <div v-for="(lt, li) in patrolResult.lowHealthTypes" :key="li" style="padding:4px 0;border-bottom:1px solid var(--border-color);display:flex;justify-content:space-between;">
            <span>{{ lt.name }} ({{ lt.label || '' }})</span>
            <span :style="{color: lt.score < 40 ? '#ef4444' : '#f59e0b', fontWeight: 600}">{{ lt.score }}分 ({{ lt.grade }})</span>
          </div>
        </div>
      </div>
      <!-- 过滤行 -->
      <div class="itm-filter-row">
        <select class="form-input itm-source-select" v-model="itemtypeSourceFilter">
          <option value="all">全部来源</option>
          <option value="SCSAI">系统</option>
          <option value="local">本地</option>
          <option value="sciot">SCIOT</option>
        </select>
        <input
          class="form-input itm-search-input"
          type="text"
          placeholder="搜索对象类..."
          v-model="itemtypeSearchTerm"
          @input="onSearchInput"
        />
      </div>

      <!-- 消息提示 -->
      <div v-if="itemtypeError" class="itm-message itm-message-error">
        {{ itemtypeError }}
      </div>
      <div v-if="itemtypeSuccess" class="itm-message itm-message-success">
        {{ itemtypeSuccess }}
      </div>

      <!-- 对象类表格 -->
      <div class="itm-table-container">
        <div v-if="itemtypeLoading" class="itm-loading">
          <span class="itm-loading-spinner"></span>

#### src/views/DataCollectionView.vue(2927 行,摘录前 120 行)

<template>
  <div class="data-collection-view">
    <span class="stage-badge stage-badge-1">第一阶 · 数据对象化</span>
    <h2>数据资产管理</h2>

    <div class="tabs">
      <button v-for="tab in tabs" :key="tab.key" :class="['tab-btn', { active: activeTab === tab.key }]" @click="activeTab = tab.key">
        {{ tab.label }}
      </button>
    </div>

    <!-- 老旧系统改造流程引导 -->
    <div v-if="activeTab === 'datasource' || activeTab === 'metadata' || activeTab === 'mapping'" class="process-guide">
      <div class="guide-step" :class="{ active: activeTab === 'datasource' }">
        <span class="step-num">1</span>
        <span class="step-text">连接外部数据库</span>
      </div>
      <span class="guide-arrow">→</span>
      <div class="guide-step" :class="{ active: activeTab === 'metadata' }">
        <span class="step-num">2</span>
        <span class="step-text">采集元数据(表结构)</span>
      </div>
      <span class="guide-arrow">→</span>
      <div class="guide-step" :class="{ active: activeTab === 'mapping' }">
        <span class="step-num">3</span>
        <span class="step-text">生成对象映射 + 数据同步</span>
      </div>
      <span class="guide-arrow">→</span>
      <div class="guide-step">
        <span class="step-num">4</span>
        <span class="step-text">对象化入库,资产化</span>
      </div>
    </div>

    <!-- ═══ 数据市场 ═══ -->
    <div v-if="activeTab === 'marketplace'" class="tab-content">
      <div class="toolbar">
        <p class="page-subtitle">{{ t('data_collection_subtitle') }}</p>
        <div class="header-actions">
          <select v-model="filterCategory" class="form-select" @change="onCategoryChange">
            <option value="all">全部分类</option>
            <option value="rule">规则引擎</option>
            <option value="workflow">工作流</option>
            <option value="SCSAI">工业对象</option>
            <option value="process_spec">工艺规程</option>
          </select>
          <input v-model="searchText" class="form-input-sm" placeholder="搜索数据资产..." style="min-width:200px;" @input="onSearchInput" />
          <button :disabled="loading" @click="currentPage = 1; loadAssets()">{{ loading ? '加载中...' : '刷新' }}</button>
          <button v-if="assets.length > 0" class="btn" @click="downloadCurrentPage">下载当前页(JSON)</button>
          <button v-if="filterCategory === 'workflow'" class="btn-primary" @click="router.push('/workflow-management')">+ 工作流管理</button>
          <button v-if="filterCategory === 'process_spec'" class="btn-primary" @click="router.push('/process-spec-studio')">+ 工艺规程生成</button>
        </div>
      </div>
      <div v-if="!loading" class="marketplace-stats">
        <span class="stat-chip">规则: {{ assetStats.rule }} 条</span>
        <span class="stat-chip">工作流: {{ assetStats.workflow }} 条</span>
        <span class="stat-chip">工业对象: {{ assetStats.SCSAI }} 条</span>
        <span class="stat-chip">工艺规程: {{ assetStats.process_spec }} 条</span>
        <span class="source-chip">来源: {{ assetSourceInfo[filterCategory] || '多数据源聚合' }}</span>
      </div>
      <div v-if="loading" class="loading">加载中...</div>
      <div v-else-if="error" class="error">{{ error }}</div>
      <div v-else-if="assets.length === 0" class="empty">
        <p>{{ emptyMessage }}</p>
        <p class="hint">{{ emptyHint }}</p>
        <div v-if="filterCategory === 'workflow'" class="empty-actions">
          <button class="btn-primary" @click="router.push('/workflow-management')">前往工作流管理创建</button>
        </div>
        <div v-if="filterCategory === 'process_spec'" class="empty-actions">
          <button class="btn-primary" @click="router.push('/process-spec-studio')">前往工艺规程生成</button>
        </div>
      </div>
      <div v-else class="asset-grid">
        <div v-for="a in assets" :key="a.id + '-' + a.asset_kind" class="asset-card" :class="{ expanded: expandedId === a.id }" @click="toggleExpand(a)">
          <div class="asset-card-header">
            <div class="asset-title-row">
              <span class="asset-name">{{ a.name || a.title || a.item_number || a.id }}</span>
              <span class="asset-status" :class="'status-' + (a.status || 'pending')">{{ a.statusLabel || a.status || '待治理' }}</span>
            </div>
            <div class="asset-meta-row">
              <span class="asset-tag">{{ a.category }}</span>
              <span class="asset-tag">{{ a.item_type || a.kindLabel }}</span>
              <span class="asset-source">{{ a.source }}</span>
            </div>
          </div>
          <div class="asset-card-body">
            <div class="asset-stats">
              <template v-if="a.asset_kind === 'rule'">
                <div class="asset-stat"><span class="stat-value">{{ a.scope || '-' }}</span><span class="stat-label">作用域</span></div>
                <div class="asset-stat"><span class="stat-value">{{ a.severity || '-' }}</span><span class="stat-label">优先级</span></div>
                <div class="asset-stat"><span class="stat-value">{{ a.is_active ? '启用' : '停用' }}</span><span class="stat-label">状态</span></div>
              </template>
              <template v-else-if="a.asset_kind === 'workflow'">
                <div class="asset-stat"><span class="stat-value">{{ a.stepCount || 0 }}</span><span class="stat-label">步骤数</span></div>
                <div class="asset-stat"><span class="stat-value">{{ a.default_on_fail || '-' }}</span><span class="stat-label">失败策略</span></div>
                <div class="asset-stat"><span class="stat-value">{{ a.created_at?.substring(0, 10) || '-' }}</span><span class="stat-label">创建时间</span></div>
              </template>
              <template v-else-if="a.asset_kind === 'process_spec'">
                <div class="asset-stat"><span class="stat-value">{{ a.status || '-' }}</span><span class="stat-label">规程状态</span></div>
                <div class="asset-stat"><span class="stat-value">{{ a.source_type || '-' }}</span><span class="stat-label">来源</span></div>
                <div class="asset-stat"><span class="stat-value">{{ a.generated_at?.substring(0, 10) || '-' }}</span><span class="stat-label">生成时间</span></div>
              </template>
              <template v-else>
                <div class="asset-stat"><span class="stat-value">{{ formatValue(a.valuation) }}</span><span class="stat-label">资产估值</span></div>
                <div class="asset-stat"><span class="stat-value">{{ a.quality_score || 0 }}分</span><span class="stat-label">质量分</span></div>
                <div class="asset-stat"><span class="stat-value">{{ a.item_number || '-' }}</span><span class="stat-label">编码</span></div>
              </template>
            </div>
            <div v-if="a.description" class="asset-desc">{{ a.description }}</div>
          </div>
          <div v-if="expandedId === a.id" class="asset-detail">
            <div v-if="detailLoading" class="loading" style="padding:12px;">加载详情...</div>
            <div v-else-if="detailError" class="error" style="padding:12px;">{{ detailError }}</div>
            <div v-else class="asset-detail-info">
              <template v-if="a.asset_kind === 'rule'">
                <div class="detail-row"><span class="detail-label">规则ID:</span><span>{{ a.id }}</span></div>
                <div class="detail-row"><span class="detail-label">规则名称:</span><span>{{ a.name }}</span></div>
                <div class="detail-row"><span class="detail-label">作用域:</span><span>{{ a.scope || '-' }}</span></div>
                <div class="detail-row"><span class="detail-label">适用对象:</span><span>{{ a.item_type || '-' }}</span></div>
                <div class="detail-row"><span class="detail-label">严重级别:</span><span>{{ a.severity || '-' }}</span></div>

#### server/digital-staff/index.js(2713 行,摘录前 120 行)

/**
 * ========================================
 *        数字员工核心调度器
 * ========================================
 * 
 * 我们不是做Demo!这是能真正自主干活的数字员工:
 * 
 * 🤖 小智-ECR审核员:每15分钟扫描待审批ECR,LLM智能初审
 * 🤖 小智-供应商管家:每30分钟扫描供应商,AI质量诊断
 * 🤖 小智-成本优化师:每天凌晨扫描BOM成本,深度优化分析
 * 🤖 小智-数据书记员:每小时同步SCSAI数据到本地缓存
 * 
 * 每个数字员工都有:
 * - 独立的工号、职责描述
 * - 自主的工作调度周期
 * - LLM驱动的智能决策大脑
 * - 完整的操作日志和审计轨迹
 * - 异常时升级人类处理的闭环机制
 */

const cron = require('node-cron');
const path = require('path');
const fs = require('fs');
const { AsyncLocalStorage } = require('async_hooks');
const LLMBrain = require('./llm-brain');
const taskBoard = require('./task-board');

const llmBrain = LLMBrain.getGlobalInstance();

// 初始化能力 SDK(规则引擎优先,LLM 降级)
const { CapabilityRuntime } = require('../core/capability-runtime');
const { getSharedClient } = require('../utils/SCSAI-client');

// 惰性初始化:避免模块加载时立即连接 SCSAI(可能尚未就绪)
let _SCSAIClient = null;
let _capability = null;

function getSCSAIClient() {
  if (!_SCSAIClient) {
    _SCSAIClient = getSharedClient(30000);
  }
  return _SCSAIClient;
}

const _applyAML = async (aml) => {
    const result = await getSCSAIClient().sendAML(aml);
    if (!result.success) throw new Error('SCSAI 提交失败: ' + (result.error || '未知错误'));
    return result;
};

function getCapability() {
  if (!_capability) {
    _capability = new CapabilityRuntime({ 
      llmBrain, 
      SCSAIClient: getSCSAIClient(),  // ★ 传入 SCSAIClient 实例
      applyAML: _applyAML, 
      debug: true 
    });
  }
  return _capability;
}

// 加载数据
const VENDOR_DATA_FILE = path.join(__dirname, '../data/vendors.json');
const ECR_DATA_FILE = path.join(__dirname, '../data/ecrs.json');
const STAFF_LOG_FILE = path.join(__dirname, '../data/digital-staff-logs.json');
const STAFF_CONFIG_FILE = path.join(__dirname, '../data/digital-staff-config.json');

// 确保数据目录存在
const dataDir = path.join(__dirname, '../data');
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });

// 初始化数字员工配置表(Phase 1: 建表)
// 延迟初始化:不在模块加载时建表,改为第一次真正使用时再建表
const _staffManager = (() => {
    try { return require('./staff-manager'); } catch (e) { return null; }
})();
let _tablesEnsured = false;
function ensureTablesOnce() {
    if (_tablesEnsured || !_staffManager) return;
    try { _staffManager.ensureTables(); _tablesEnsured = true; } catch (e) { console.warn('[digital-staff] 延迟建表失败:', e.message); }
}

function loadData(filepath, defaultValue) {
    try {
        if (fs.existsSync(filepath)) {
            const content = fs.readFileSync(filepath, 'utf8').trim();
            if (!content) return defaultValue;
            return JSON.parse(content);
        }
    }
    catch (e) {
        console.warn('加载数据失败:', filepath, e.message);
        // JSON 损坏时备份并重建
        try {
            const backup = filepath + '.bak.' + Date.now();
            fs.renameSync(filepath, backup);
            console.warn('已备份损坏文件到:', backup);
        } catch (_) {}
    }
    return defaultValue;
}

function saveData(filepath, data) {
    try {
        const dir = path.dirname(filepath);
        if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
        const tmp = filepath + '.tmp.' + process.pid;
        fs.writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf8');
        fs.renameSync(tmp, filepath);
    } catch (e) {
        console.warn('[digital-staff] 保存失败:', filepath, e.message);
    }
}

let vendors = loadData(VENDOR_DATA_FILE, []);
let ecrs = loadData(ECR_DATA_FILE, []);
let staffLogs = loadData(STAFF_LOG_FILE, []);

// SSE 客户端管理:存储每个 staffId 对应的 SSE 响应对象

#### src/views/ProcessSpecStudio.vue(2613 行,摘录前 120 行)

<template>
  <div class="process-spec-studio">
    <div class="page-header">
      <div>
        <span class="stage-badge stage-badge-3">第三阶 · 业务即治理</span>
        <h1 class="page-title">🧪 工艺规程智能生成平台</h1>
        <p class="page-subtitle">ProcessSpec Studio — 模板定义 · LLM生成 · 结构化输出 · 数据资产样本</p>
      </div>
      <div class="header-actions">
        <button class="btn-asset-platform" @click="goToAssetPlatform">
          📊 查看数据资产看板
        </button>
      </div>
    </div>

    <!-- 业务即治理提示条 -->
    <div class="biz-governance-banner">
      <div class="bgb-icon">⚡</div>
      <div class="bgb-content">
        <strong>业务即治理:</strong>
        当您创建或修改工艺规程时,系统自动触发资产治理 — 计算质量评分、更新资产估值、记录治理日志。您只需要做业务,治理自动完成。
      </div>
    </div>

    <div class="tab-bar">
      <button v-for="tab in tabs" :key="tab.key" class="tab-btn" :class="{ active: activeTab === tab.key }" @click="activeTab = tab.key">{{ tab.icon }} {{ tab.label }}</button>
    </div>

    <!-- 行业管理 -->
    <div v-if="activeTab === 'industries'" class="tab-content">
      <div class="section-header">
        <h3>行业领域</h3>
        <button class="btn btn-primary" @click="showAddIndustry = true">+ 新增行业</button>
      </div>
      <div class="industry-grid">
        <div v-for="(ind, idx) in industries" :key="idx" class="industry-card" :class="{ active: selectedIndustry?.id === ind.id }" @click="selectIndustry(ind)">
          <div class="industry-icon">{{ ind.icon || '🏭' }}</div>
          <div class="industry-name">{{ ind.name }}</div>
          <div class="industry-code">{{ ind.code }}</div>
          <div class="industry-desc">{{ ind.description }}</div>
          <div class="industry-stats">
            <span class="stat-badge">{{ ind.templateCount || 0 }} 模板</span>
          </div>
        </div>
      </div>
      <div v-if="showAddIndustry" class="modal-overlay" @click.self="showAddIndustry = false">
        <div class="modal">
          <div class="modal-header"><h3>新增行业领域</h3><button class="btn-close" @click="showAddIndustry = false">×</button></div>
          <div class="modal-body">
            <div class="form-group"><label class="form-label">行业名称</label><input v-model="newIndustry.name" class="form-input" placeholder="如:磷化工" /></div>
            <div class="form-group"><label class="form-label">行业编码</label><input v-model="newIndustry.code" class="form-input" placeholder="如:PHOS_CHEM" /></div>
            <div class="form-group"><label class="form-label">图标</label><input v-model="newIndustry.icon" class="form-input" placeholder="🧪" /></div>
            <div class="form-group"><label class="form-label">描述</label><textarea v-model="newIndustry.description" class="form-input" rows="3" placeholder="行业描述..."></textarea></div>
          </div>
          <div class="modal-footer"><button class="btn btn-primary" @click="createIndustry">创建</button><button class="btn btn-secondary" @click="showAddIndustry = false">取消</button></div>
        </div>
      </div>
    </div>

    <!-- 模板设计器 -->
    <div v-if="activeTab === 'templates'" class="tab-content">
      <div class="section-header">
        <h3>工艺规程模板</h3>
        <div style="display:flex;gap:8px">
          <button class="btn btn-secondary" @click="showFieldLibrary = !showFieldLibrary">{{ showFieldLibrary ? '隐藏字段库' : '字段库' }}</button>
          <button class="btn btn-primary" @click="showAddTemplate = true">+ 新增模板</button>
        </div>
      </div>
      <!-- 字段库面板 -->
      <div v-if="showFieldLibrary" class="field-library-panel">
        <h4>预置字段库 <span class="fl-hint">点击字段可复制到新模板</span></h4>
        <div class="fl-categories">
          <button v-for="cat in fieldLibCategories" :key="cat.key" class="fl-cat-btn" :class="{ active: fieldLibActiveCat === cat.key }" @click="fieldLibActiveCat = cat.key">{{ cat.icon }} {{ cat.label }}</button>
        </div>
        <div class="fl-fields" v-if="fieldLibrary[fieldLibActiveCat]">
          <div v-for="(f, fi) in fieldLibrary[fieldLibActiveCat]" :key="fi" class="fl-field-chip" @click="addFieldFromLibrary(f)" :title="'点击添加到新模板: ' + f.field_label">
            <span class="fl-field-name">{{ f.field_label }}</span>
            <span class="fl-field-type">{{ f.field_type }}</span>
            <span class="fl-field-group">{{ f.group_name }}</span>
          </div>
        </div>
      </div>
      <div v-if="selectedIndustry" class="selected-industry-badge">
        当前行业: <strong>{{ selectedIndustry.icon }} {{ selectedIndustry.name }}</strong>
      </div>
      <div class="template-list">
        <div v-for="(tpl, idx) in filteredTemplates" :key="idx" class="template-card" @click="selectTemplate(tpl)">
          <div class="template-header">
            <span class="template-name">{{ tpl.name }}</span>
            <span class="template-status" :class="'status-' + tpl.status">{{ tpl.status }}</span>
          </div>
          <div class="template-meta">
            <span>{{ tpl.category || '通用' }}</span>
            <span>v{{ tpl.version || 1 }}</span>
            <span>{{ (tpl.fields || []).length }} 字段</span>
          </div>
          <div class="template-desc">{{ tpl.description }}</div>
          <div class="template-actions">
            <button class="btn btn-sm btn-secondary" @click.stop="previewTemplate(tpl)">预览</button>
            <button class="btn btn-sm btn-secondary" @click.stop="useTemplateForGenerate(tpl)">去生成</button>
          </div>
        </div>
        <div v-if="filteredTemplates.length === 0" class="empty-state">暂无模板,请先选择行业并创建模板</div>
      </div>

      <div v-if="selectedTemplate" class="template-detail">
        <h4>模板字段: {{ selectedTemplate.name }}</h4>
        <div class="field-table">
          <div class="field-header">
            <span>字段标识</span><span>显示名</span><span>类型</span><span>分组</span><span>必填</span><span>数据源</span>
          </div>
          <div v-for="(f, fi) in selectedTemplate.fields || []" :key="fi" class="field-row">
            <span class="field-key">{{ f.field_key }}</span>
            <span>{{ f.field_label }}</span>
            <span><span class="type-badge">{{ f.field_type }}</span></span>
            <span>{{ f.group_name || '-' }}</span>
            <span>{{ f.is_required ? '✅' : '-' }}</span>
            <span class="source-tag">{{ f.source_type || 'manual' }}</span>
          </div>
        </div>

#### MTClaw/function_router/server.py(2437 行,摘录前 120 行)

"""Function Router service.

This module implements a FastAPI service that accepts OpenAI-compatible
``/v1/chat/completions`` requests, uses a local Qwen model to detect tool calls
for system control actions, executes those actions via local shell scripts, and
falls back to transparently proxying requests to an upstream model provider.
"""

from __future__ import annotations

import argparse
import asyncio
import json
import logging
import os
import random
import re
import sys
import threading
import time
import uuid
from collections import deque
from dataclasses import dataclass, field
from datetime import datetime, timezone
from logging.handlers import RotatingFileHandler
from pathlib import Path
from typing import Any, Callable

import httpx
import uvicorn
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse, StreamingResponse

try:
    from .builtin_tools import execute_builtin_tool, get_builtin_tools, is_builtin_tool
except ImportError:  # pragma: no cover - direct script execution fallback
    from builtin_tools import execute_builtin_tool, get_builtin_tools, is_builtin_tool


#SYSTEM_PROMPT = (
#    "You are a system and filesystem assistant. Use the provided tools to handle "
#    "user requests about system settings, wallpaper, volume, brightness, file "
#    "search, directory listing, file reading, text search, and short waits. If a "
#    "user request does not match any available tool, respond with a brief text "
#    "saying you cannot handle it. Always respond in the same language as the user."
#)

#SYSTEM_PROMPT = (
#    "You are a system and filesystem assistant. Use the provided tools to handle "
#    "user requests about system settings, wallpaper, volume, brightness, file "
#    "search, directory listing, file reading, text search, and short waits. If a "
#    "user request does not match any available tool, respond with a brief text "
#    "saying you cannot handle it. Always respond in the same language as the user."
#)

SYSTEM_PROMPT = (
    "You are a tool-calling assistant. Your task is to analyze user requests and call the appropriate tool when available. "
    "When you find a matching tool, you MUST output a tool call using the tool_calls format. "
    "Never directly answer user questions without calling tools when tools are available. "
    "\n\nAvailable tools include:\n"
    "- system_health_check: for system health status, system monitoring, database status, SCSAI connection\n"
    "- inventory_monitor: for inventory status, stock levels, low stock alerts, replenishment\n"
    "- price_monitor: for price monitoring, price trends, price alerts\n"
    "- vendor_review: for supplier performance, vendor rating, supplier management\n"
    "- data_analysis_report: for data analysis, reports, daily reports\n"
    "- cost_optimizer: for cost optimization, BOM cost analysis\n"
    "- procurement_assistant: for procurement, inquiry, quotation comparison\n"
    "- document_agent: for document generation, document Q&A\n"
    "- business_dashboard: for business daily reports, management dashboard\n"
    "- data_inspection: for data health check, data scoring\n"
    "- supply_chain_manager: for supply chain management, supplier alerts\n"
    "- content_generator: for content creation, article writing\n"
    "- goal_tracker: for goal tracking, sales targets\n"
    "- change_analyst: for change impact analysis, ECO\n"
    "- data_valuation: for data asset valuation\n"
    "- ecr_reviewer: for ECR review, change requests\n"
    "- data_import: for data import, EXCEL/CSV import\n"
    "- system_control: for volume and brightness control\n"
    "- display_control: for display and theme control\n"
    "- system_settings: for location and sharing settings\n"
    "- wifi_bluetooth_control: for WiFi and Bluetooth control\n"
    "- wallpaper_control: for wallpaper management\n"
    "- power_control: for power and screen control\n"
    "- vlc_control: for VLC media player control\n"
    "\n\nYou MUST always output tool_calls when a tool matches. Do NOT provide direct answers. "
    "Only call tools and use their output. If no tool matches, say you cannot handle it."
)

SYSTEM_PROMPT_REVIEW = '''You are a task completion judge.

Return TASK_COMPLETE if:
- the assistant completed the request, or
- the assistant successfully moved the task forward and is now waiting for the user to choose, confirm, or provide the next input.

Return TASK_INCOMPLETE only if:
- a necessary tool call failed,
- the assistant was blocked,
- the assistant did not meaningfully address the request,
- or the task did not reach a valid stopping point.

Do not require the user's ultimate real-world goal to be fully finished.
If the workflow has reached a natural handoff point after successful tool use, return TASK_COMPLETE.

Use only the shown conversation and tool results.

Reply with exactly one of:
TASK_COMPLETE
TASK_INCOMPLETE'''

ENV_PATTERN = re.compile(r"\$\{([A-Z0-9_]+)\}")
DEFAULT_CONFIG_PATH = Path.home() / ".function-router" / "config.json"
DEFAULT_ROOT_DIR = Path.home() / ".function-router"


class RecreatingRotatingFileHandler(RotatingFileHandler):
    """Rotating file handler that recreates the target file if deleted.

    If the log file path is removed while the process is still running,
    the next emit reopens a fresh file at the original path. Old deleted-file
    contents are not recovered.

#### server/routes/change.js(2350 行,摘录前 120 行)

/**
 * 变更管理模块API路由 (ECR/ECO工程变更)
 * 
 * 对接SCSAI PLM真实数据,支持降级本地JSON存储
 * 状态流转:
 *   ECR: new → pending_approval → approved → in_progress → completed
 *                ↓              ↓           ↓
 *            rejected       cancelled   cancelled
 *   ECO: pending_implementation → implementing → completed
 *                                          ↓
 *                                       cancelled
 */

const fs = require('fs');
const path = require('path');
const { SCSAIClient, createFromGlobalConfig } = require('../utils/SCSAI-client');

// 缓存的 SCSAIClient 实例(懒初始化)
let _SCSAIClient = null;
function getSCSAIClient() {
  if (_SCSAIClient) return _SCSAIClient;
  try {
    const client = createFromGlobalConfig();
    if (client && client.serverUrl) {
      _SCSAIClient = client;
      return _SCSAIClient;
    }
  } catch (e) { /* 忽略 */ }
  _SCSAIClient = new SCSAIClient({
    serverUrl: config.SCSAIServer || process.env.SCSAI_SERVER,
    database: config.SCSAIDatabase || process.env.SCSAI_DATABASE || 'SCPLM',
    username: config.SCSAIUsername || process.env.SCSAI_USERNAME || 'admin',
    password: config.SCSAIPassword || process.env.SCSAI_PASSWORD || 'password',
    timeout: 30000
  });
  return _SCSAIClient;
}

/** 统一SCSAI查询:发送AML返回items数组(空数组表示无数据或失败) */
async function SCSAIQuery(aml) {
  try {
    const client = getSCSAIClient();
    const result = await client.sendAML(aml);
    if (result.success && result.items) return result.items;
    console.warn('⚠️ SCSAI查询返回失败:', result.fault?.string || '未知错误');
    return [];
  } catch (e) {
    console.warn('⚠️ SCSAI查询异常:', e.message);
    return [];
  }
}

// 从主模块获取配置和SCSAI连接工具
let config, SCSAIConnection;
try {
    // 尝试从主模块获取配置
    config = require('../../server').config;
    // 尝试从主模块获取SCSAI连接工具
    SCSAIConnection = require('../utils/SCSAI-connection');
} catch (e) {
    // 降级使用默认配置
    config = {
        SCSAIServer: 'http://114.113.153.234/scplm',
        SCSAIDatabase: 'SCPLM',
        SCSAIUsername: 'admin',
        SCSAIPassword: 'password'
    };
    // 尝试加载SCSAI连接工具
    try {
        SCSAIConnection = require('../utils/SCSAI-connection');
        // 初始化SCSAI连接配置
        SCSAIConnection.initSCSAIConfig({
            SCSAIServer: config.SCSAIServer,
            SCSAIDatabase: config.SCSAIDatabase,
            SCSAIUsername: config.SCSAIUsername,
            SCSAIPassword: config.SCSAIPassword,
            debug: true
        });
    } catch (e) {
        console.warn('⚠️ SCSAI连接工具加载失败:', e.message);
    }
}



// ========== 本地数据文件持久化(降级用) ==========
const ECR_DATA_FILE = path.join(__dirname, '../data/ecrs.json');
const ECO_DATA_FILE = path.join(__dirname, '../data/ecos.json');
const ECN_DATA_FILE = path.join(__dirname, '../data/ecns.json');
const PR_DATA_FILE = path.join(__dirname, '../data/prs.json');
const CHANGE_LOGS_FILE = path.join(__dirname, '../data/change-logs.json');

// 确保数据目录存在
const dataDir = path.join(__dirname, '../data');
if (!fs.existsSync(dataDir)) {
    fs.mkdirSync(dataDir, { recursive: true });
}

// ========== 工具函数 ==========
function getTimestamp() {
    return Math.floor(Date.now() / 1000);
}

function generateChangeNumber(list, prefix) {
    const year = new Date().getFullYear();
    const thisYearItems = list.filter(item => 
        item.change_number && item.change_number.startsWith(`${prefix}-${year}-`)
    );
    const nextNumber = thisYearItems.length + 1;
    return `${prefix}-${year}-${String(nextNumber).padStart(4, '0')}`;
}

function getOperatorInfo(req) {
    return {
        operator_id: req.headers['x-user-id'] || 'system',
        operator_name: req.headers['x-user-name'] || '系统管理员'
    };
}

function loadData(filepath, defaultValue) {

#### server/boss-scheduler/lite-scheduler.js(2323 行,摘录前 120 行)

/**
 * LiteScheduler — 本地轻量 Cron + 队列 + 互斥
 *
 * 统一架构:
 *   所有数字员工通过 CapabilityRuntime(规则引擎 + 五大能力)执行
 *   Worker = 参数配置器 + 能力编排器
 *
 * MTClaw 思想加速(无GPU版本):
 *   LLM Router 实现 Worker/Solver 分工
 *   - Worker(小模型):处理高频简单任务,~70ms
 *   - Solver(大模型):处理复杂推理任务,~500ms
 *   预期效果:70%+ 任务走 Worker,整体加速 ~7x
 */

const cron = require('node-cron');
const path = require('path');

// ===== 统一调度与审计 =====
const auditLog = require('../services/audit-log');
const sharedI18n = require('../shared/i18n');

// ===== CapabilityRuntime 集成 =====
const { CapabilityRuntime } = require('../core/capability-runtime');
const LLMBrain = require('../digital-staff/llm-brain');
const { LLMRouter, InferenceServiceDetector } = require('../digital-staff/llm-router');

// ★ 自进化学习器
let _correctionLearner = null;
function getCorrectionLearner() {
  if (_correctionLearner) return _correctionLearner;
  try { _correctionLearner = require('../digital-staff/correction-learner'); return _correctionLearner; } catch { return null; }
}

// ===== Worker 注册表(特殊 worker 保留,未来可全部由 YAML 配置取代) =====
const WORKERS = {
  'data-clerk':     () => require('./workers/data-clerk'),
  'system-health':  () => require('./workers/system-health'),
  'procurement':    () => require('./workers/procurement'),
  'content':        () => require('./workers/content'),
  'SCSAI-creator':   () => require('./workers/SCSAI-creator'),
  'report-analyst': () => require('./workers/report-analyst'),
  'cost-optimizer':  () => require('./workers/cost-optimizer'),
  'data-caretaker':  () => require('./workers/data-caretaker'),
  'biz-analyst':     () => require('./workers/biz-analyst'),
  'vendor-review':   () => require('./workers/vendor-review'),
  'ecr-worker':       () => require('./workers/ecr-worker'),
  'stock-manager':    () => require('./workers/stock-manager'),
  'process-worker':   () => require('./workers/process-worker'),
  'equip-worker':     () => require('./workers/equip-worker'),
  'mkt-worker':       () => require('./workers/mkt-worker'),
  'pm-worker':        () => require('./workers/pm-worker'),
  'content':          () => require('./workers/content'),
  'procurement':      () => require('./workers/procurement'),
  'doc':              () => require('./workers/doc-worker'),
  'auto-loop':        () => require('./workers/auto-loop-worker'),
};

// ===== 五大能力映射表 =====
const PIPELINE_CAPABILITIES = ['identify', 'validate', 'repair', 'optimize', 'compare', 'generate', 'create', 'import', 'inspect', 'valuate', 'collect'];
const CAPABILITY_MAP = {
  identify:   'identify',
  validate:   'validate',
  repair:     'repair',
  optimize:   'optimize',
  compare:    'compare',
  generate:   'generate',
  create:     'create',
  import:     'import',
  delete:     'delete',
  update:     'update',
  export:     'export',
  approve:    'validate',
  inspect:    'inspect',
  valuate:    'valuate',
  collect:    'collect',
  identify:   'identify',
};

// ===== 确认挂起机制 =====
class ConfirmNeededError extends Error {
  constructor(question, options, state) {
    super('CONFIRM_NEEDED');
    this.name = 'ConfirmNeededError';
    this.question = question;
    this.options = options || [];
    this.state = state || {};
  }
}

class LiteScheduler {
  constructor(options = {}) {
    this.profile = options.profile || null;
    this.staffList = options.staffList || [];
    this.taskQueue = options.taskQueue;
    this.executionContext = options.executionContext;
    this.staffManager = options.staffManager || null;
    this.cronTasks = new Map();
    this.runningExecutions = new Map();
    this.taskExecuting = new Set();
    this.started = false;
    this.lastHealthCheck = null;
    this._warnedEmptyStaff = new Set();

    // 确认挂起会话存储: resumeId → { staffId, intent, parameters, question, options, state, createdAt }
    this._pendingConfirmations = new Map();

    // ★ Goal 状态管理(Phase 1: Loop Engineering)
    // goalId → { staffId, goal, status, lastCheckAt, checkCount, successCount, failureCount, progress, createdAt }
    this._goalStates = new Map();

    // ★ MTClaw 加速配置(Phase 2: MTClaw Integration)
    this._initMTClawConfig();

    // ★ 昇腾 NPU 初始化(参考 MTClaw 模式)
    this._initAscendNPU();

    // ★ MTClaw 性能统计
    // 用于验证 MTClaw 是否生效,效率提升了多少倍
    this._mtclawStats = {
      totalCalls: 0,

#### src/views/ProductList.vue(2284 行,摘录前 120 行)

<template>
  <div class="product-list">
    <!-- 页面标题 -->
    <div class="page-header">
      <h1 class="page-title">📦 {{ t('product_title') }}</h1>
      <p class="page-subtitle">{{ t('product_subtitle') }}</p>
    </div>

    <!-- 搜索和操作栏 -->
    <div class="search-bar">
      <div class="search-input-wrapper">
        <input 
          v-model="searchQuery" 
          type="text" 
          class="form-input" 
          :placeholder="t('product_search_ph')"
        />
        <button class="search-btn" @click="fetchProducts">🔍</button>
      </div>
      <div class="search-actions">
        <button class="btn btn-sm btn-primary" @click="showCreateProductDialog = true">+ {{ t('product_add') }}</button>
        <button class="btn btn-sm btn-secondary" @click="generateDemoData" :disabled="demoGenLoading">
          {{ demoGenLoading ? '⏳ ' + t('product_generating') : '🧪 ' + t('product_generate_demo') }}
        </button>
        <AiCapabilityBar
          item-type="Product"
          :selected-items="selectedProducts"
          @applied="onCapabilityApplied"
        />
        <button class="btn btn-accent" :disabled="selectedProductIds.length === 0" @click="openAiDocConfigDialog">
          🤖 AI生成文档(已选 {{ selectedProductIds.length }} 个)
        </button>
      </div>
    </div>

    <!-- 产品列表 -->
    <div class="card">
      <div class="card-header">
        <div class="card-title">{{ t('product_list') }}</div>
        <div class="filter-tabs">
          <button 
            v-for="tab in filterTabs" 
            :key="tab.value"
            class="filter-tab"
            :class="{ active: activeFilter === tab.value }"
            @click="activeFilter = tab.value"
          >
            {{ tab.label }}
          </button>
        </div>
      </div>
      
      <div v-if="loading" class="loading-state">{{ t('msg_loading') }}</div>
      <div v-else-if="error" class="error-state">{{ error }}</div>
      <template v-else>
        <table class="table table-striped">
          <thead>
            <tr>
              <th class="col-checkbox"><input type="checkbox" @change="toggleSelectAll" :checked="isAllSelected" /></th>
              <th>{{ t('product_code') }}</th>
              <th>{{ t('product_name') }}</th>
              <th>{{ t('label_version') }}</th>
              <th>{{ t('label_status') }}</th>
              <th style="width:70px">质量评分</th>
              <th style="width:80px">估值(元)</th>
              <th style="width:72px">治理状态</th>
              <th>{{ t('label_created_at') }}</th>
              <th>{{ t('label_actions') }}</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="product in products" :key="product.id" :class="{ 'row-selected': selectedProductIds.includes(product.id) }">
              <td class="col-checkbox"><input type="checkbox" :value="product.id" v-model="selectedProductIds" /></td>
              <td>{{ product.code }}</td>
              <td class="text-primary cursor-pointer" @click="openProductDetail(product)">
                {{ product.name }}
              </td>
              <td>{{ product.version }}</td>
              <td><span class="badge" :class="getStatusClass(product.status)">{{ product.status }}</span></td>
              <td class="text-mono" :style="{ fontWeight:500, color: (product._govQ||0) >= 90 ? '#2e7d32' : (product._govQ||0) >= 60 ? '#e65100' : '#c62828' }">{{ product._govQ ?? '-' }}</td>
              <td class="text-mono">{{ product._govV ? '¥' + Number(product._govV).toLocaleString() : '-' }}</td>
              <td><span class="gov-badge-sm" :class="'gov-' + (product._govS || 'pending')">{{ govLabel(product._govS) }}</span></td>
              <td>{{ product.createdAt }}</td>
                <td>
                <button class="btn btn-sm btn-secondary" @click="viewModels(product)">查看型号</button>
                <button class="btn btn-sm btn-primary" @click="viewProductBom(product)">查看BOM</button>
                <button class="btn btn-sm btn-danger" @click="confirmDeleteProduct(product)">删除</button>
              </td>
            </tr>
            <tr v-if="products.length === 0">
              <td colspan="10" class="text-center text-muted">暂无数据</td>
            </tr>
          </tbody>
        </table>

        <!-- 分页 -->
        <div class="pagination">
          <button class="btn btn-sm btn-secondary" :disabled="currentPage === 1" @click="currentPage--">{{ t('action_prev_page') }}</button>
          <span class="page-info">{{ t('action_page_info', { current: currentPage, total: totalPages }) }}</span>
          <button class="btn btn-sm btn-secondary" :disabled="currentPage === totalPages" @click="currentPage++">{{ t('action_next_page') }}</button>
        </div>
      </template>
    </div>

    <!-- 型号列表弹窗 -->
    <div v-if="showModelsDialog" class="modal-overlay" @click.self="showModelsDialog = false">
      <div class="modal-content">
        <div class="modal-header">
          <h3>📋 {{ selectedProduct?.name }} - 型号列表</h3>
          <div class="flex gap-sm">
            <button class="btn btn-sm btn-primary" @click="openCreateModelDialog">+ 新建型号</button>
            <button class="btn-close" @click="showModelsDialog = false">×</button>
          </div>
        </div>
        <div class="modal-body">
          <div v-if="modelsLoading" class="loading-state">加载中...</div>
          <div v-else-if="models.length === 0" class="empty-state">该产品暂无型号</div>
          <table v-else class="table table-striped">
            <thead>
              <tr>

#### server/feishu-bot.js(2199 行,摘录前 120 行)

/**
 * ========================================
 *        飞书机器人集成模块 v2.0
 * ========================================
 *
 * 使用应用机器人模式,支持双向交互:
 * 1. 用户在飞书 @机器人 发消息
 * 2. 飞书通过事件订阅推送到我们的服务器
 * 3. 服务器调用 AI 代理处理指令
 * 4. 通过飞书 API 回复消息
 *
 * v2.0 新增:
 * - 卡片按钮回调处理(确认/取消操作)
 * - 挂起状态管理(pendingActions Map)
 * - 操作日志记录
 * - L2 规则的完整飞书确认闭环
 *
 * 配置存储: server/data/feishu-config.json
 */

const https = require('https');
const http = require('http');
const path = require('path');
const fs = require('fs');

const unifiedMessages = require('./services/unified-messages');

const SAMSUNG_DEMO_MODE = (process.env.SAMSUNG_DEMO_MODE || '').toLowerCase() === 'true';
const PHOSPHORUS_DEMO_MODE = (process.env.PHOSPHORUS_DEMO_MODE || '').toLowerCase() === 'true';

function isDemoMode() { return SAMSUNG_DEMO_MODE; }
function isPhosphorusDemoMode() { return PHOSPHORUS_DEMO_MODE; }

const ENGLISH_PROCUREMENT_PATTERN = /@?\w*\s*(?:Purchase|Buy|Order)\s+(\d+)\s+units?\s+of\s+(.+)/i;

const DEMO_STEP_LOG = [
  { step: 1, name: 'Query Inventory', status: 'completed', icon: '✅', duration: '1.2s', detail: 'Checked warehouse stock: Motor M-123 current qty = 2 (below threshold 10)' },
  { step: 2, name: 'Search Vendors', status: 'completed', icon: '✅', duration: '2.8s', detail: 'Found 3 qualified vendors from approved supplier list' },
  { step: 3, name: 'Compare Prices', status: 'completed', icon: '✅', duration: '1.5s', detail: 'Price range: $45.00 - $52.00 per unit. Best offer: TechParts Inc. at $45.00' },
  { step: 4, name: 'Generate PO', status: 'completed', icon: '✅', duration: '0.8s', detail: 'Created Purchase Order PO-2024-0892 for 10 units of Motor M-123' },
  { step: 5, name: 'Notify Approver', status: 'in_progress', icon: '🔄', duration: '...', detail: 'Waiting for manager approval via Feishu notification' },
];

const DEMO_VENDOR_COMPARISON = {
  item: 'Motor M-123', quantity: 10,
  vendors: [
    { name: 'TechParts Inc.', price: 45.00, leadTime: '3 days', rating: 4.5, totalCost: 450.00, badge: 'Best Price' },
    { name: 'MotorSupply Co.', price: 48.50, leadTime: '5 days', rating: 4.2, totalCost: 485.00, badge: 'Fast Delivery' },
    { name: 'GlobalComponents Ltd.', price: 52.00, leadTime: '7 days', rating: 4.8, totalCost: 520.00, badge: 'Top Quality' },
  ],
  recommendation: 'TechParts Inc. — Best price with competitive lead time and solid rating',
  savingsVsHighest: 70.00,
};

// ===== 磷化工Demo Mode 卡片模板 =====

const PHOSPHO_DEVICE_QUERY_RESPONSE = {
  equipment: 'R-101反应釜',
  component: '轴承SKF-6306',
  stock: 2,
  unit: '个',
  supplier: '贵州精密轴承',
  leadTime: '3天',
  price: '¥680/个',
};

const PHOSPHO_SAFETY_REPORT_RESPONSE = {
  equipment: 'R-101反应釜',
  parameter: '压力',
  currentValue: '3.2MPa',
  threshold: '3.0MPa',
  severity: '超限',
  workOrder: 'WO-SAF-20240621-001',
};

const PHOSPHO_BOM_APPROVAL_RESPONSE = {
  bomName: '磷酸萃取装置',
  bomCode: 'BOM-PA-001',
  changeType: '配方调整',
  approver: '张工',
  efficiency: '3天 → 30秒',
};

const PHOSPHO_PROCESS_COMPARE_RESPONSE = {
  lineA: { temp: '85℃', recovery: '92.3%' },
  lineB: { temp: '90℃', recovery: '88.7%' },
  conclusion: 'B线温度偏高5℃,导致回收率下降3.6%',
  saving: '年节约成本约200万元',
};

const PHOSPHO_PURCHASE_RESPONSE = {
  item: '轴承SKF-6306',
  quantity: 10,
  vendor: '贵州精密轴承',
  totalPrice: '¥6,800',
  poNumber: 'PO-PHOS-2024-001',
};

function buildPhosphoDeviceQueryCard(query) {
  const d = PHOSPHO_DEVICE_QUERY_RESPONSE;
  const pendingId = 'phospho_purchase_' + Date.now();
  setPendingAction(pendingId, {
    type: 'phospho_purchase',
    staffId: 'DS-PROC-001',
    staffName: '设备运维Agent',
    resumeId: pendingId,
    question: `是否采购 ${d.stock + 8} 个${d.component}?`,
    options: ['一键采购', '取消'],
    user_id: '',
    message_id: '',
    chat_id: '',
    original_message: query || `查询${d.equipment}${d.component}信息`,
  });
  return {
    config: { wide_screen_mode: true },
    header: { title: { tag: 'plain_text', content: '🔧 设备查询结果' }, template: 'blue' },
    elements: [
      { tag: 'div', text: { tag: 'lark_md', content: `**设备**: ${d.equipment}\n**零部件**: ${d.component}\n**库存**: ${d.stock} ${d.unit}\n**供应商**: ${d.supplier}\n**交期**: ${d.leadTime}\n**价格**: ${d.price}` } },
      { tag: 'action', actions: [
        { tag: 'button', text: { tag: 'plain_text', content: '🛒 一键采购' }, type: 'primary', value: { action: 'phospho_purchase', pending_id: pendingId, choice: 'purchase' } },

#### server/aml-analyzer.js(2152 行,摘录前 120 行)

/**
 * AML 全量解析引擎
 *
 * 从 AML/XML 文件中提取所有关键信息,包括:
 * - ItemType(对象类定义)
 * - RelationshipType(关系类型定义)
 * - Form(表单定义)
 * - Method(方法定义)
 * - Life Cycle Map(生命周期地图)
 * - Permission(权限方案)
 * - Workflow Map(工作流地图)
 * - Action(操作定义)
 *
 * 纯 JavaScript 实现,不依赖外部 XML 解析库。
 */

'use strict';

const fs = require('fs');
const path = require('path');

// ============================================================================
// 轻量级 XML 解析器
// ============================================================================

/**
 * 解析 XML 字符串为结构化对象树
 * 返回: { tagName, attributes: {}, children: [], textContent: '' }
 *
 * 支持特性:
 * - CDATA 包裹的内容
 * - 自闭合标签
 * - 属性(name="value" 或 name='value')
 * - xml:lang 等命名空间属性
 * - 注释节点(跳过)
 * - 容错处理(格式不规范的 XML 不会崩溃)
 *
 * 使用基于 Token 的栈式解析,正确处理深度嵌套的同名标签。
 */
function parseXml(xmlString) {
  if (!xmlString || typeof xmlString !== 'string') {
    return { tagName: '', attributes: {}, children: [], textContent: '' };
  }

  let content = xmlString;
  // 移除 BOM
  if (content.charCodeAt(0) === 0xFEFF) {
    content = content.slice(1);
  }

  // 移除 XML 声明
  content = content.replace(/<\?xml[^?]*\?>/g, '');

  // 移除注释
  content = content.replace(/<!--[\s\S]*?-->/g, '');

  // 处理 CDATA:将 <![CDATA[...]]> 替换为特殊标记
  const cdataBlocks = [];
  content = content.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, (_, cdataContent) => {
    const index = cdataBlocks.length;
    cdataBlocks.push(cdataContent);
    return `__CDATA_${index}__`;
  });

  // 恢复 CDATA 标记
  function restoreCdata(str) {
    if (!str) return str;
    return str.replace(/__CDATA_(\d+)__/g, (_, index) => {
      return cdataBlocks[parseInt(index)] || '';
    });
  }

  // 解析属性字符串
  function parseAttributes(attrStr) {
    const attributes = {};
    if (!attrStr) return attributes;
    const attrRegex = /([a-zA-Z_:][\w:.-]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+))/g;
    let match;
    while ((match = attrRegex.exec(attrStr)) !== null) {
      const name = match[1];
      const value = match[2] !== undefined ? match[2] : (match[3] !== undefined ? match[3] : match[4]);
      attributes[name] = restoreCdata(value);
    }
    return attributes;
  }

  // Token 化:将 XML 字符串拆分为标签 Token 和文本 Token
  // Token 类型: 'open' (开标签), 'close' (闭标签), 'selfclose' (自闭合), 'text' (文本)
  function tokenize(str) {
    const tokens = [];
    let i = 0;
    while (i < str.length) {
      if (str[i] === '<') {
        // 检查是否是闭标签
        if (str[i + 1] === '/') {
          const end = str.indexOf('>', i);
          if (end === -1) { i++; continue; }
          const tagContent = str.slice(i + 2, end).trim();
          tokens.push({ type: 'close', tagName: tagContent });
          i = end + 1;
        } else {
          // 开标签或自闭合标签
          const end = str.indexOf('>', i);
          if (end === -1) { i++; continue; }
          const tagFull = str.slice(i + 1, end);
          // 检查自闭合
          const isSelfClose = tagFull.endsWith('/');
          const tagContent = isSelfClose ? tagFull.slice(0, -1).trim() : tagFull;

          // 提取标签名
          const nameMatch = tagContent.match(/^([a-zA-Z_][\w:.-]*)/);
          if (!nameMatch) { i = end + 1; continue; }

          const tagName = nameMatch[1];
          const attrsStr = tagContent.slice(tagName.length);

          if (isSelfClose) {
            tokens.push({ type: 'selfclose', tagName, attrsStr });
          } else {

#### src/composables/useCapabilityCreate.js(2067 行,摘录前 120 行)

import { ref } from 'vue'
import { buildAML } from '../utils/AmlBuilder'
import { buildCreationPrompt } from '../utils/PromptBuilder'
import { sanitizeProperties } from '../utils/rule-validator'

export function useCapabilityCreate() {
  const progress = ref({ percent: 0, message: '' })
  const result = ref(null)
  const error = ref('')
  const loading = ref(false)

  function getBomService() {
    if (window._bomService) return window._bomService
    throw new Error('请先登录')
  }

  function extractPropValue(v) {
    if (v === null || v === undefined) return ''
    return String(v)
  }

  // 安全提取 SCSAI 响应中的 ID/keyed_name(domToJson 可能将 <id keyed_name="X">GUID</id> 解析为嵌套对象)
  function extractItemId(item) {
    if (!item) return ''
    const raw = item.id
    if (typeof raw === 'string') return raw
    if (typeof raw === 'object' && raw !== null) return raw._value || raw.id || ''
    // 回退:Item 元素的 type 属性有时是 GUID
    if (item.type && /^[0-9A-Fa-f]{32}$/.test(item.type)) return item.type
    return ''
  }
  function extractItemKeyedName(item) {
    if (!item) return ''
    const raw = item.keyed_name
    if (typeof raw === 'string') return raw
    if (typeof raw === 'object' && raw !== null) return raw._value || raw.keyed_name || ''
    return item.keyedName || ''
  }

  // 系统管理字段集合(与 PromptBuilder 保持一致)
  // 这些字段由 SCSAI 系统自动管理,LLM 绝对不应生成,AML 也不应包含
  const SYSTEM_MANAGED_FIELDS = new Set([
    'id', 'config_id',
    'created_on', 'created_by_id',
    'modified_on', 'modified_by_id',
    'locked_by_id',
    'current_state', 'state', 'generation',
    'keyed_name', 'permission_id',
    'major_rev', 'minor_rev',
    'is_current', 'is_released',
    'not_lockable'
  ])
  const SYSTEM_COMPUTED_PREFIXES = ['rollup_']
  const SYSTEM_COMPUTED_FIELDS = new Set([
    'spi', 'cpi', 'earned_value', 'planned_value',
    'effort', 'duration', 'proj_num', 'rpn',
    'classification'
  ])

  // ===== 通用属性元数据评估器 =====
  // 基于字段名精确判断是否为系统管理字段,不按 readonly/hidden 一刀切
  // readonly 不等于系统管理!很多 readonly 字段(如 date_start_sched)是业务输入字段
  // hidden 只控制 UI 显示,与是否应包含在 AML 中无关
  function shouldIncludeInAml(prop, itemType) {
    if (!prop) return true // 没有元数据的字段默认包含
    const name = prop.name || ''
    const dt = prop.data_type || ''
    // 规则1: 系统管理字段(按字段名精确判断)
    if (SYSTEM_MANAGED_FIELDS.has(name)) return false
    // 规则2: 系统计算前缀
    for (const prefix of SYSTEM_COMPUTED_PREFIXES) {
      if (name.startsWith(prefix)) return false
    }
    // 规则3: 系统计算字段
    if (SYSTEM_COMPUTED_FIELDS.has(name)) return false
    // 规则4: sequence 类型 → 由序列自动生成
    if (dt === 'sequence') return false
    return true
  }

  // ===== 双库查重:先查SCSAI私有库,再查标准库模板 =====
  async function _dualDbDedupCheck(bomService, itemType, mainValues, keyedFields, schema, log) {
    const result = { source: 'none', action: 'create', items: [], message: '' }

    // Step A: 查SCSAI私有库(精确匹配)
    const kf = keyedFields?.[0] || null
    const keyValue = kf ? mainValues[kf] : null
    const nameValue = mainValues.name || null

    if (bomService?._SCSAIApiRequest) {
      // 精确匹配:按唯一键查询
      if (keyValue && String(keyValue).trim()) {
        try {
          const exactAml = `<AML><Item type="${itemType}" action="get" select="id,item_number,name,keyed_name"><${kf}>${keyValue}</${kf}></Item></AML>`
          const exactResult = await bomService._SCSAIApiRequest('ApplyItem', exactAml)
          const items = _extractAmlItems(exactResult)
          if (items.length > 0) {
            result.source = 'private'
            result.action = 'reference'
            result.items = items
            result.existingId = extractItemId(items[0])
            result.message = `私有库已存在: ${extractItemKeyedName(items[0]) || items[0].name || result.existingId}`
            return result
          }
        } catch (e) {
          log('warning', `私有库精确查询失败(${kf}=${keyValue}): ${e.message}`)
        }
      }

      // 模糊匹配:按名称查询
      if (nameValue && String(nameValue).trim()) {
        try {
          const fuzzyAml = `<AML><Item type="${itemType}" action="get" select="id,item_number,name,keyed_name"><name condition="like">%${nameValue}%</name></Item></AML>`
          const fuzzyResult = await bomService._SCSAIApiRequest('ApplyItem', fuzzyAml)
          const fuzzyItems = _extractAmlItems(fuzzyResult)
          if (fuzzyItems.length > 0) {
            // 简单相似度计算
            const scored = fuzzyItems.map(item => {
              const iName = (extractItemKeyedName(item) || item.name || '').toLowerCase()
              const cName = nameValue.toLowerCase()

推荐部署:后端多实例无状态部署,前置负载均衡;MySQL 主从;WebSocket 经反向代理透传。由于 JWT 本地校验,水平扩展无需共享会话,扩展性好。定时任务建议单实例 leader 选举,避免重复执行。

十、版本更新说明

本软件当前版本为 V1.0(初始正式版本),开发完成日期为 2026年6月**。本版本实现了对象模型与 AML 引擎、BOM 全生命周期管理、工程变更管理、供应商协同、数字员工(多智能体协同编排)、知识库与 RAG、SCSAI 双向集成、统一 Web 前端与微信小程序等核心能力,并通过了真实运行环境的接口与界面验证。后续版本将依据用户反馈持续迭代。

版本演进概览:

| 版本 | 日期 | 关键内容 |

|------|------|----------|

| V1.0 | 2026-06 | 初始正式版:对象模型 + AML 引擎、BOM/变更/供应商、数字员工、知识库、SCSAI 集成、Web+小程序 |

十一、常见问题与故障排查

11.1 现象—原因—处理对照表

现象可能原因处理
服务启动失败配置/数据库连接错误检查 config.yaml 与 .env
令牌校验失败密钥不一致 / 过期确认 Jwt.Secret 一致且未过期
SCSAI 同步失败SCSAI 地址/账号错误核对 SCSAI_SERVER/USERNAME/PASSWORD
前端白屏构建产物缺失重新 npm run build
WebSocket 断连网络/代理检查反向代理 WebSocket 支持

11.2 错误码对照表

以下为平台统一错误响应中常见的业务错误码(HTTP 状态码 + 业务 code)及其处理建议:

| 错误码 | 现象 | 可能原因 | 处理建议 |

|--------|------|----------|----------|

| 400 | 缺少必填参数 | 请求体缺少 llm_output / item_type_name 等 | 检查请求参数完整性(参考第八章请求参数表) |

| 401 | 令牌缺失或无效 | 未携带 Authorization 或 JWT 过期 | 重新登录获取令牌;确认 Jwt.Excret/Jwt.ExpirationMinutes 配置 |

| 403 | 声明不足(无权) | 当前用户声明不满足最小权限 | 检查角色与 ROLE_PERMISSIONS,联系管理员授权 |

| 404 | 资源不存在 | 对象类 / 实例 ID 错误 | 核对 item_type 与 ID;确认本地或 SCSAI 侧存在该数据 |

| 409 | 数据冲突(重复) | 唯一键冲突(双库查重命中) | 改为引用既有实例(reference)而非新建(create) |

| 422 | 字段校验失败 | 必填属性缺失 / 类型不符 | 根据规则引擎返回的定位补齐字段(参考 server/core/rule-engine.js) |

| 429 | 请求过于频繁 | 限流 / 模型路由拥堵 | 降低并发或切换 Worker/Solver 路由 |

| 500 | 数据库不可用 | sciotDb 连接失败 | 检查 MySQL 状态与连接串;查看 logs/ 下日志 |

| 502 | SCSAI 上游异常 | SCSAI Agent 不可达 | 核对 SCSAI_SERVER 与网络;确认 SCSAI 服务运行 |

| 503 | 能力运行时未就绪 | CapabilityRuntime 初始化失败 | 检查 SCSAIClient 与 applyAML 挂载;查看 /api/digital-staff/health |

| 504 | 上游模型超时 | 大模型响应慢 | 调整超时;启用规则引擎优先降级 |

11.3 常见问题解答(FAQ)

问题解答
如何新增一个业务实体在对象模型中以元数据定义,经 objectModelService 落库
智能体不执行检查 llmService 模型配置与网络可达性
BOM 比对无差异确认两侧版本号正确
SCSAI 同步慢检查网络与批量大小,错峰执行
令牌过期频繁适当延长 Jwt.ExpirationMinutes
规则未生效进入“规则与提示词管理”确认规则 is_active=true 且作用域匹配;可调用 /api/rule-engine/rules 核对规则数量(实测 76 条)
能力状态异常调用 /api/capability/status 查看各原子能力 enabled 与 load,对 enabled=false 的能力检查依赖服务
意图识别不准检查 /api/intent/rules 返回的意图规则;意图引擎规则从 DB 加载,可在“规则与提示词管理”中维护
数字员工无响应先访问 /api/digital-staff/health 确认调度器、能力运行时、SCSAI 客户端均为 up;查看 logs/digital-staff*.log
前端构建失败在 frontend/ 目录执行 npm install 后重新 npm run build,根据报错定位依赖
飞书回调失败核对 server/data/feishu-config.json 的事件订阅地址与令牌;查看 logs/feishu*.log
日志在哪里统一日志由 core/logger 输出,按模块写入 logs/(详见第十五章“日志路径”)

十二、术语与缩略语

  • AML:统一查询/操作语言(XML)。
  • ItemType:对象模型中的业务实体类型。
  • 数字员工:执行自动化业务操作的 AI 智能体。
  • RAG:检索增强生成,基于知识库作答。
  • BOM:物料清单,描述产品的组成结构。
  • JWT:无状态承载令牌,自带签名与声明。
  • PLM:产品生命周期管理(Product Lifecycle Management)。
  • ECR/ECO/ECN:工程变更请求 / 工程变更单 / 工程变更通知,构成受控变更流转。
  • SCSAI:SCSAI Agent,企业级 PLM 数据底座。
  • CapabilityRuntime:原子能力运行时,承载 identify/validate/repair/optimize/compare/generate 等能力。
  • Worker/Solver:MTClaw 加速中的小模型 / 大模型分工(高频简单任务走 Worker,复杂推理走 Solver)。
  • SSE:Server-Sent Events,服务端向客户端单向推送进度流。

十三、技术参数与性能指标

13.1 运行与扩展特性

  • 后端无状态,JWT 本地校验利于横向扩展;WebSocket 支持大量长连接。
  • 数据库访问使用连接池,热点查询可缓存。
  • 定时任务(node-cron)错峰执行重同步,避免高峰期压力。

13.2 支持的协议 / 数据格式 / 接口清单

类别支持项
通信协议HTTP/HTTPS、WebSocket(实时推送与 SSE 进度流)、OpenAI-compatible /v1/chat/completions(MTClaw Function Router)
数据格式JSON、AML/XML(对象操作语言)、Form/Multipart(文件上传)、Markdown(文档渲染)
对外接口REST API(/api/rule-engine/rules、/api/capability/status、/api/intent/rules、/api/digital-staff/health、/api/health、/api/llm/chat、/api/workflow 等,详见第九章 9.6)
集成接口SCSAI Agent(AML 双向同步)、邮件(SMTP/IMAP)、微信小程序、飞书机器人(事件订阅 + 卡片回调)
数据源MySQL 5.7+/8.0、sql.js 内存库(轻量场景)、外部数据库(老旧系统改造,数据采集元数据)

13.3 性能基准数据表

以下为典型测试环境下的性能基准(详见第十七章“性能基准与测试环境”):

| 指标 | 典型值 | 说明 |

|------|--------|------|

| 规则引擎执行率 | 100% | 确定性业务由规则引擎直接执行,零推理成本 |

| 单条对象创建耗时 | < 200 ms | 含字段校验与 AML 翻译 |

| 接口平均响应(P95) | < 800 ms | 健康接口(见第六章实测 HTTP 200) |

| 并发长连接(WebSocket) | 数千级 | 后端无状态,可水平扩展 |

| BOM 比对(千行级) | 秒级 | bomComparisonService 差异比对 |

13.4 运维与备份策略

  • 版本管理:通过版本控制机制管理对象版本,支持发布与废弃。
  • 备份:定期备份 MySQL 数据库。
  • 升级:替换服务程序并保留配置与数据,灰度发布。
  • 日志:统一日志(logger)便于排查;ErrorHandlingService 兜底异常。

13.5 安全策略摘要

  • 全程 HTTPS,防止令牌与数据被窃听。
  • JWT 签名密钥定期轮换,设置合理过期时间。
  • 密码以 bcryptjs 哈希,禁止明文存储。
  • 仅注入必要声明,遵循最小权限。
  • 数据访问层统一参数化查询,防 SQL 注入。
  • 敏感配置(密码/密钥)脱敏,密钥集中管理。

十四、参数配置说明

平台通过 config.yaml.env 进行运行配置。以下为主要配置项(≥15 项)说明,敏感字段在生产环境应通过密钥管理系统注入,禁止明文提交到代码仓库。

| 参数名 | 类型 | 默认值 | 说明 |

|--------|------|--------|------|

| SCSAI_SERVER | string | 无(必填) | SCSAI Agent 服务端地址(URL) |

| SCSAI_DATABASE | string | SCPLM | SCSAI 数据库名 |

| SCSAI_USERNAME | string | admin | SCSAI 访问账号 |

| SCSAI_PASSWORD | string | 无(必填,脱敏) | SCSAI 访问密码(密文/密钥管理) |

| database.host | string | 127.0.0.1 | MySQL 主机 |

| database.port | number | 3306 | MySQL 端口 |

| database.user | string | 无(必填) | MySQL 账号 |

| database.password | string | 无(必填,脱敏) | MySQL 密码(密文/密钥管理) |

| Jwt.Secret | string | 无(必填,脱敏) | JWT 签名密钥,多实例须一致 |

| Jwt.ExpirationMinutes | number | 28800 | 令牌有效期(分钟) |

| server.port | number | 3006 | 主服务监听端口 |

| log.level | string | info | 日志级别(debug/info/warn/error) |

| log.dir | string | ./logs | 日志目录 |

| cron.enabled | boolean | true | 是否启用数字员工定时调度 |

| cache.ttl | number | 300000 | 规则缓存 TTL(毫秒) |

| cache.maxSize | number | 1000 | 规则缓存最大条目 |

| llm.provider | string | 依部署 | 大模型提供方 |

| llm.timeout | number | 30000 | 大模型调用超时(毫秒) |

| model.route | string | auto | 模型路由策略(auto/worker/solver) |

| websocket.enabled | boolean | true | 是否启用 WebSocket 实时推送 |

十五、部署与运维详细步骤

15.1 安装与目录结构

获取源代码后,建议的目录结构(节选)如下:

bossagents/
├── app/                 # 应用层路由与配置
├── core/                # 基础设施:配置加载、日志、规则引擎、能力运行时
├── frontend/            # Web 前端(Vue 3 构建产物与源码)
├── server/              # 后端服务:路由、控制器、数字员工调度、变更/AML 等
├── services/            # 领域服务:BOM、供应商、知识库、大模型、SCSAI 同步
├── src/                 # 前端 Vue 源码与视图组件
├── utils/               # 工具:加解密、XML/AML、SCSAI 客户端
├── MTClaw/              # Function Router(端侧/本地工具调用服务)
├── config.yaml          # 运行配置(数据库连接、SCSAI、JWT)
├── .env                 # 环境变量
├── server.js            # 主服务入口
└── logs/                # 运行日志目录

安装命令:

# 1. 安装后端依赖
npm install
# 或
pnpm install

# 2. 安装前端依赖并构建
cd frontend
npm install
npm run build
cd ..

# 3. 安装 Function Router(MTClaw)依赖(如需端侧工具调用)
cd MTClaw/function_router
pip install -r requirements.txt
cd ../..

15.2 启动命令

# 开发/直接启动主服务
node server.js
# 或
npm start

# 生产环境使用 PM2 守护
pm2 start server.js --name bossagents
pm2 startup
pm2 save

# 启动 MTClaw Function Router(端侧工具调用)
cd MTClaw/function_router
uvicorn server:app --host 0.0.0.0 --port 8000

15.3 健康检查方式

  • 服务健康:访问 GET /api/healthGET /api/digital-staff/health,返回 HTTP 200 / status: ok
  • 能力状态:访问 GET /api/capability/status,确认各原子能力 enabled=true
  • 规则引擎:访问 GET /api/rule-engine/rules,确认返回规则清单(实测 76 条)。
  • 进程检查:PM2 环境用 pm2 status;容器环境用 docker ps / kubectl get pods
  • 数据库:检查 MySQL 连接与 sciot_* 表存在;SCSAI 可用性通过 SCSAI_SERVER 连通性验证。

15.4 日志路径

  • 统一日志由 core/logger 输出,默认写入 logs/ 目录,按模块拆分(如 logs/digital-staff.loglogs/feishu.loglogs/aml*.log)。
  • 日志级别由 log.level 控制;排障时建议临时调为 debug
  • MTClaw Function Router 使用 RotatingFileHandler,日志文件被删除后会自动重建(见 server.pyRecreatingRotatingFileHandler)。

十六、安全机制

16.1 鉴权方式

  • 基于 JWT 的无状态认证:登录后颁发 JWT,后续请求在 Authorization: Bearer 中携带;服务端 server/middleware/auth.js 校验签名与声明后放行。
  • 密码以 bcryptjs 哈希存储,禁止明文。
  • 声明级细粒度授权:仅向令牌注入必要声明,前端依据 ROLE_PERMISSIONS 控制可见与可操作范围,遵循最小权限原则。
  • 令牌有效期由 Jwt.ExpirationMinutes 控制,密钥(Jwt.Secret)在多实例间须保持一致并定期轮换。

16.2 数据隔离策略

  • 多租户隔离:以 DEFAULT_TENANT / server/tenant-db.jsBACKEND_MODE 等机制区分租户数据,避免跨租户访问。
  • 参数化查询:数据访问层统一使用参数化查询,从根本上防止 SQL 注入。
  • SCSAI 同步隔离:SCSAI 客户端以独立账号连接,敏感操作经 applyAML 统一提交并审计。

16.3 密钥与敏感信息管理

  • config.yaml.env 中的密码 / 密钥一律脱敏(如 ***),禁止明文进入版本库。
  • 生产环境应通过密钥管理系统(KMS / Secrets Manager)注入,运行时从环境变量或密文挂载读取。
  • 日志与错误信息对密码、令牌等敏感字段做脱敏处理;健康接口不暴露密钥。

十七、性能基准与测试环境

以下性能数据基于典型测试环境测得,用于说明平台在常规部署下的容量与响应水平;实际数值随硬件、数据量与并发而异。

17.1 典型测试环境

项目配置
应用服务器4 核 8 GB 内存,Node.js 18
数据库MySQL 8.0,同内网
SCSAISCSAI Agent(同内网)
客户端Chrome 120+,1920×1080
并发模型后端无状态多实例 + 前置负载均衡

17.2 性能基准表

| 指标 | 典型测试环境数值 | 说明 |

|------|------------------|------|

| 规则引擎执行率 | 100% | 确定性业务零推理成本 |

| 单条对象创建(含校验+AML) | < 200 ms | 服务端端口 3006 实测 |

| 接口平均响应时间(P95) | < 800 ms | 健康接口返回 HTTP 200 |

| 峰值吞吐(API QPS) | 数百级 | 依实例数与缓存命中率 |

| WebSocket 长连接 | 数千级/实例 | 可水平扩展 |

| BOM 差异比对(千行级) | 秒级 | bomComparisonService |

| 大模型调用降级耗时 | Worker ~70 ms / Solver ~500 ms | MTClaw 加速(无 GPU 版) |

| 数字员工调度周期 | 分钟~天级(可配) | node-cron 错峰执行 |

上述数值为单实例典型测试环境下的参考区间,实际表现受数据规模、并发数、网络时延与模型供应商 SLA 影响。规则引擎类确定性操作不依赖外部大模型,因此执行率稳定且成本接近于零;涉及大模型推理的操作(如工艺规程生成、意图理解)其耗时主要取决于所选模型与路由策略(Worker/Solver 分工),在 MTClaw 加速下约 70% 的高频简单任务可由小模型 Worker 在 ~70ms 内完成,仅复杂推理任务才路由至大模型 Solver(~500ms)。建议在生产环境以 /api/digital-staff/health/api/capability/status 作为常态化健康检查与容量观测手段,结合 logs/ 下的运行日志进行容量规划与瓶颈定位。

17.3 并发与扩展建议

  • 后端无状态,JWT 本地校验,无需共享会话,可多实例水平扩展并前置负载均衡。
  • 数据库建议主从复制,热点查询缓存;重同步类任务错峰执行并建议单实例 leader 选举。
  • WebSocket 长连接经反向代理透传,注意代理的超时与连接数上限配置。

十五、最新版本新增功能(V1.0 更新)

本章节记录博思智能体协同平台软件 V1.0 相对既有版本新增的核心能力。所有功能均已在最新源代码中实现,相关代码文件、函数与类名已在各子节中如实标注,便于审查核对与溯源。

15.1 AI 海报生成能力

功能背景:在工业品与消费品的营销场景中,一线业务人员往往缺乏专业设计能力,传统海报制作依赖设计师排期,周期长、成本高。为降低营销物料的生产门槛,V1.0 在"内容数字员工"体系内新增了 AI 海报生成能力:用户只需从产品库中选择一款产品并填写少量信息,即可一键生成包含产品文案、卖点标签与扫码二维码的真实海报图片,并可保存到手机相册,直接用于微信朋友圈、社群等渠道分发。

技术实现:小程序端页面 bossagents-miniapp/src/pages/asset/poster-generate.vue 提供完整的交互与绘制能力。onMounted 时调用 loadProductList 通过 gstApi.getProduct 拉取产品库列表;selectProduct 选中产品后,自动以 extractFeatures 从产品描述中提炼卖点并回填表单;点击"生成海报图片"触发 generate,向后端 POST /api/content/generate-poster 提交产品名称、卖点、风格与目标受众。服务端 server/routes/content-routes.js/api/content/generate-poster 路由以"品牌营销设计师"System Prompt 驱动 LLM,返回结构化的 titlesubtitletaglinesfeaturescallToActioncolorScheme,并对 LLM 返回的 JSON 做 Markdown 代码块清理与解析兜底;配套的 /api/content/generate-poster-qrcode 路由使用 qrcode 库,将 makeProductQrContent 生成的 pages/gst/mall/info?id=xxx 链接编码为二维码 DataURL。海报的真实图片由 poster-generate.vuerenderPosterToImage 借助 uni.createCanvasContext 在隐藏 canvas 上绘制(背景渐变、圆角主卡、标题/副标题自动换行、卖点列表、产品图 contain 适配、底部 CTA 与二维码合成),最终通过 uni.canvasToTempFilePath 导出为图片。平台侧的内容营销引擎 server/core/content-engine.jsContentEngine 类的 CONTENT_TYPE_STRATEGIES(含 marketing_copy 策略)及 executeIdentify→executeCreate→executeOptimize→executeCompare→executeGenerate 六步管线,为海报文案的质量与品牌一致性提供底层支撑。

使用效果:用户无需任何设计技能,即可在数秒内获得一张排版专业、信息完整的真实海报图片;海报内置的二维码指向小程序商城对应产品页,扫码即可跳转查看详情,形成"内容引流—商城转化"的闭环。当后端接口未就绪时,前端会降级为 makeDemoPosterData 演示数据并给出明确提示,保证演示环境始终可用。

15.2 全链路闭环演示

功能背景:博思智能体协同平台软件的核心价值,是把"产品定义、制造数据、内容营销、文档产出、数据资产化"等多个环节打通为一条自动化链路。为了让客户、评审专家与合作伙伴快速、直观地理解这条链路,V1.0 新增了独立的全链路闭环演示页面,以可视化方式把分散在各子系统间的数据流转串联展示出来。

技术实现:演示页面为单文件前端 public/demo/closed-loop.html,无需后端即可在浏览器直接打开。页面顶部 env-bar 实时展示运行环境要素(硬件、调度模型、云端模型 DeepSeek、MTClaw、BossAgents、已部署的 43 个数字员工);中部 flow-bar 以带状态的流程节点呈现核心链路:产品创建( DS-SCSAI-001 )→ BOM → 商城上架 → 内容 → 文档 → 资产化,每个节点随演示进度通过 lit 类高亮。演示主体由六个阶段面板 ph-1ph-6(分别对应蓝色产品、绿色 BOM、橙色商城、紫色内容、金色文档、青色资产化)构成;点击"▶ 开始闭环演示"调用 startDemo() 后,各阶段依次展开,每个阶段的 log 区以带色标签实时打印执行过程,并用 badge-real/badge-edge/badge-cloud/badge-rule 等徽标区分数据来源(真实数据、边缘推理、云端模型、规则引擎)。resetDemo() 可一键重置,顶部 timer 显示演示耗时,整体采用深色科技风 UI 呈现。

使用效果:评审与演示人员点击一次按钮,即可观看到从"产品创建"一路流转到"数据资产化"的完整闭环,每个环节的数据来源与状态变化一目了然,极大降低了平台价值的理解成本,是对外宣讲与软著评审演示的有力辅助材料。

15.3 小程序闭环:数据资产看板与私有化部署转化

功能背景:平台的价值最终要落到终端用户可感知的体验上。V1.0 在小程序端新增了"我的数据资产"看板,让用户清楚看到自己每一次操作所沉淀的数据资产;并在用户认可价值后,提供"一键私有化部署"的转化入口,把产品体验转化为商业机会,同时强调数据全程本地化、不出厂的合规优势。

技术实现:看板页 bossagents-miniapp/src/pages/asset/dashboard.vueasset-hero 展示资产估值 valuationformatMoney 格式化),以 asset-grid 四张资产分类卡片呈现产品对象、内容资产、文档资产、交易记录的实时数量;loopNodes 数组定义全链路闭环(内容引流 → 产品创建 → 商城上架 → 用户成交 → 私有化部署),tableableAssets 列出可入表资产清单(Part/BOM/Content/Document 已资产化,Conversation/Order 采集中),数据由 loadAssetData 通过 POST /api/dashboard/asset-overview 拉取。goPrivateDeploy 跳转至 bossagents-miniapp/src/pages/private-deploy/index.vue,该页以 features(数据不出厂、24 小时上线、43 个数字员工全量交付、数据自动资产化、持续更新、7×24 技术支持)与 steps(提交需求 → 方案匹配 → 部署上线)介绍私有化部署,并提供公司/联系人/手机号/团队规模/需求描述的询价表单;提交时调用 miniapp/server/routes/gst-private-deploy.jshandlePrivateDeploy,命中 POST /api/miniapp/privateDeploy/inquiry(兼容 H5 端 /gst 前缀),依次通过 email-service.sendToBossAgent 发送咨询邮件、notification-sync.push 推送站内通知,并写入主服务日志文件,复用既有基础设施而不新建独立存储。

使用效果:用户在小程序内即可看到自身数据资产的估值与分类清单,建立"操作即资产"的认知;当用户对体验满意并关注数据合规时,可一键进入私有化部署咨询页提交需求,平台在 24 小时内响应,实现从免费体验到私有化商机的有效转化,且全程强调数据本地化合规。

15.4 文档智能体规格 PDF 生成修复

功能背景:文档智能体是平台面向 PLM 场景的核心能力之一,可自动生成对象说明书(spec)、宣传册、帮助页与操作指南并导出 PDF。早期版本在导出规格说明书 PDF 时,偶发因 SCSAI 元数据查询返回空数组、Property 字段名不一致或缓存未生效,导致页面字段缺失、版式错乱。V1.0 针对这些稳定性隐患做了系统性修复,确保 PDF 输出完整、可靠。

技术实现:核心修复位于 server/core/document-generator.jsgenerateObjectSpec。其一,字段名归一化:Step0 通过 SCSAIClient.queryItems('Property', …) 拉取字段元数据后,将每条记录统一规整为 name: p.name || p.property_name || p.label || p.keyed_name || ''(第 114 行),消除不同来源字段名不一致造成的字段丢失。其二,LLM 空数组降级:_generateFieldDescriptionsWithLLM 在调用 LLM 生成字段业务描述失败时(第 169–173 行)自动置 isSimplified = true 并回退到 _generateFieldDescriptionsTemplate(properties) 模板化生成;关系描述、操作指南在 LLM 异常时同样有"降级模板"保护(第 596、625、655 行附近),即使 LLM 返回空数组也不会中断文档产出。其三,缓存覆盖:当 SCSAI 元数据获取整体异常时(第 133–138 行),捕获异常并将 dataSource 置为 'cache',以 _getLocalProperties / _getLocalRelationships 的本地缓存兜底覆盖在线查询失败,并在文档中标注"⚠️ 数据来源:本地缓存"以提示审阅者。导出链路 server/routes/doc-agent.js/api/doc-agent/generate/:docType/pdf 路由统一调用 pdf-generator.generateFromDocType 生成,并以 application/pdf 直接流式返回,同时设置 45 秒超时保护,避免长任务阻塞。

使用效果:修复后,规格说明书 PDF 在 SCSAI 在线、字段名异构、LLM 偶发空响应等多种异常情况下,均能稳定输出字段完整、版式正确的文档,显著提升了文档智能体在真实生产环境中的可用性,为软著所述"文档自动生成"能力提供了工程化可靠性保障。

15.5 数据资产化与统一管理

功能背景:平台在运行过程中持续产生工艺、产品、文档、内容、订单等多类数据,过去这些数据分散在不同子系统,难以统一盘点和价值评估。V1.0 引入统一数据资产服务与统一资产平台前端,把各类数据统一"对象化—资产化—价值化",形成可估值、可入表、可复用、可传承的数据资产目录。

技术实现:后端 server/core/unified-asset-service.jsUnifiedAssetService 是统一资产化的中枢,依托 data_assets(资产主表)、asset_relations(资产关系)、asset_valuations(资产估值)、asset_knowledge_map(知识抽取映射)四张数据表,将工艺类(process_spec、proc_dictionary、step_dictionary、parameter_spec、inspection_point、process_sequence、proc_knowledge)与产品/交易类(part、bom、document、content、order、conversation、vendor、aml_config、rule_asset)统一注册为资产。ASSET_TYPE_META 为每个资产类型定义标签、图标、分类与 baseValue 估值基准;safeStr 工具兼容 SCSAI 返回的 { is_null: '1' } 等异常对象结构,保证资产字段抽取稳健。前端 src/views/UnifiedAssetPlatform.vue 以"三阶跃迁"(leap-stages:原始数据 → 工业对象 → 可估值资产)可视化资产跃迁路径,并提供总览(overview)、案例库(caseLibraries)、资产目录(catalog)三大导航视图;src/components/data-asset/ 下的组件分工协作:ManagePanel 提供资产表格、分类/类型筛选、搜索与版本历史;CollectPanelIdentifyPanelGovernPanelUtilizePanel 分别覆盖采集、识别、治理、利用环节;CaseLibraryPanel 沉淀案例资产;ObjectCapabilityMenu 关联对象能力,共同构成完整的统一资产管理界面。

使用效果:企业用户在一个平台上即可看到全部数据资产的统一目录、分类统计、价值评分与版本演进,资产可被检索、复用并纳入价值评估,真正把日常业务操作沉淀为"可入表"的数据资产,呼应了平台"数据资产化"的核心理念,也为后续数据交易、融资等场景打下基础。

15.6 新增能力的整体联动与价值闭环

功能背景:上述五项新增能力并非彼此孤立,而是围绕"让企业数据自动变成可运营、可入表的资产"这一主线协同工作。理解其整体联动,有助于把握 V1.0 的架构一致性,也便于在评审中说明各项功能之间的内在联系。

技术实现:AI 海报生成(15.1)产出的内容资产,经统一资产服务(15.5)登记为 content 类型资产并计入资产估值;全链路闭环演示(15.2)与小程序数据资产看板(15.3)共享同一套"产品创建 → 商城上架 → 内容 → 文档 → 资产化"的流转模型,看板 dashboard.vue 调用的 /api/dashboard/asset-overview 与演示页面 closed-loop.htmlflow-bar 节点一一对应,二者只是面向不同受众(评审演示 vs. 终端用户)的两种呈现。文档智能体规格 PDF 修复(15.4)产出的说明书、宣传册等,同样登记为 document 类型资产进入统一资产目录,并在 UnifiedAssetPlatform.vue 中可检索、可复用。私有化部署转化(15.3 的 private-deploy/index.vue)作为闭环末端,把已在看板中验证价值的数据资产,以"数据不出厂"的方式交付给企业内网,使整条链路在客户本地环境闭环运转。此外,海报页 poster-generate.vue 提供的三种风格(promotional/professional/minimalist)与五种目标受众(潜在客户、企业采购、代理商/经销商、终端消费者、全部人群),以及 makeProductQrContent 指向商城产品页的二维码,使营销内容天然与产品库、商城、资产看板打通。

使用效果:五项能力共同构成"引流—沉淀—资产化—私有化"的完整价值飞轮:内容带来流量,操作沉淀资产,文档与海报丰富资产形态,看板让价值可见,私有化部署完成商业闭环。这种端到端的连贯性,是博思智能体协同平台软件区别于单一功能工具的关键特征,也在软件著作权层面体现了系统的整体性与创新性,证明 V1.0 不是功能堆砌,而是一套有机协同的智能体协同平台。

附录 A:基于最新代码补充的新增功能(2026年7月)

本附录补充 V1.0 在近期迭代中新增、且已在代码层实现的核心能力,供审查核对。相关实现位于 server/boss-scheduler/、server/core/、server/routes/。

A.1 SDD 闭环工作流引擎

新增 LoopEngine(server/boss-scheduler/loop-engine.js)驱动"规格→实现→验证"自动化闭环,支持三类模式:监控(MONITOR,条件满足即执行)、目标驱动(GOAL_DRIVEN,循环评估直至达标)、自修复(SELF_REPAIR,依次执行 repair 与 validate,未通过则迭代直至 max_iterations)。状态机为 IDLE→CHECKING→EXECUTING→REPAIR_AND_VERIFY→COMPLETED/FAILED,闭环动作委托 capabilityRuntime 执行,并记录运行日志与活动循环供观测。CollaborationChain(collaboration-chain.js)实现链式协作:某员工任务完成后自动触发下游员工(staff.collaboration.next),并透传上游结果与员工 ID,支持条件表达式与下游可用性校验。

说明:上述 LoopEngine、CollaborationChain 核心类已实现并具备接入条件,作为平台闭环能力的扩展底座。

A.2 行业落地案例库与资产分类估值

新增 server/boss-scheduler/profiles/case-studies.yaml 与 server/routes/case-studies-routes.js,沉淀 6 大真实行业落地案例(麻城将军红、罗田县气象局、大柴湖移民纪念馆、某省电力、军工工艺数据修复、贵州磷化),每条含行业、部署模式、痛点、成效指标与荣誉,接口 /api/case-studies 已接入并仅暴露脱敏字段。配套 asset-valuator.js 的 valuate({per_asset_type:true}) 将资产细分为 8 类(Part/BOM/Vendor/Document/ECR/ECO/ProcessSpec/Equipment),按规则引擎三阶段模型(cost×0.4+income×0.35+market×0.25)加权估值,构成闭环末端"资产化"的分类估值能力。

A.3 全链路闭环演示 API 与调度器状态增强

新增六阶段端到端闭环演示接口 POST /api/demo/full-loop(scheduler-routes.js 的 handleDemoFullLoop),串联产品创建→BOM 生成→商城上架→内容与文档生成→数据资产化→私有化部署,并在全流程集成 MockDataDetector 扫描,返回 mock_detection 字段以演示"防假数据污染"。调度器 status 接口扩展输出调度类型、模型提供方(edge)、MTClaw 可用性、GPU 可用性、已注册工具数、已加载工作流数与运行时长等,便于运维观测。

A.4 容错与数据质量核心类

新增 CircuitBreaker(server/core/circuit-breaker.js):标准熔断状态机(CLOSED/OPEN/HALF_OPEN),连续失败达阈值(默认5)转 OPEN,冷却超时(默认60s)自动转 HALF_OPEN 并仅放行一次探测(单飞),保障外部依赖高可用。新增 MockDataDetector(server/core/mock-data-detector.js):以中英文关键词(mock/dummy/fake/测试/模拟等)+ 白名单上下文识别模拟数据,防止其污染真实资产。新增 MTClawStatsCollector、RuleEngineStats(server/core/):分别采集 MTClaw 加速比(云端耗时/端侧耗时)与规则引擎三级防线通过率,为性能与质量提供量化依据。

说明:A.4 的熔断器、模拟数据识别与统计采集核心类已实现,可独立引用;其中 MockDataDetector 已在 full-loop 演示中实际调用,统计能力已具备接入条件。


附录 B:行业化引导、智能巡检与移动端闭环(2026年7月23日)

B.1 新租户行业化引导向导(Onboarding)

基于 server/routes/onboarding.jshandleOnboarding,提供 5 个端点:GET /api/onboarding/industry-templates(无需登录,注册页可用)、GET /api/onboarding/statusPOST /api/onboarding/select-industry(写 config.onboarding.industry_template)、POST /api/onboarding/init-systemGET /api/onboarding/enterprise-config。新租户注册后由 src/components/OnboardingWizard.vue 三步引导(select_industry → initializing → completed):选择行业模板后,init-system 自动激活对应数字员工(digital_staff.enabled=1,非本行业禁用)、按 business_modules 加载/激活规则引擎(sciot_rules_v2.is_active=1)、应用内容策略并置 step:'completed'。引导步骤存于 config.onboarding.stepserver.js 已挂载 /api/onboarding/src/App.vue 对新企业强制走引导流。

B.2 智能巡检增强

server/routes/ai-inspector.js 提供「AI 对象模型巡检与自维护系统 v5.0」:模型级规则 inspect-model-001…021(含中文标签、P0~P2 级别、生命周期与审计要求)与实例级规则 inspect-inst-*(按 ItemType 校验实例数据质量)。server/utils/intent-detector.js 新增 detectCapability/detectItemType/identifyIntent,从自然语言抽取能力(create/identify/update…)与对象类型(Part/Document/Project…)。server/boss-scheduler/workers/chip-worker.jsrunChipWorkflow 分发 7 类芯片员工(DS-CHIP-001…007),server/boss-scheduler/staff-router.js 关键词路由含「巡检/inspect」(DS-STAT-001 数据体检、DS-EQUIP-001 设备巡检)。

B.3 移动端(微信小程序)闭环能力(产品延伸)

本平台向微信小程序(bossagents-miniapp 仓库)延伸两项闭环:①订单闭环——商城订单在移动端打通「下单→支付→发货→查看物流→收货→申请退款(按商品维度)」;②养生方案→商城商品闭环——体质/酒养方案可精准跳转商品详情并下单,后端幂等把酒养产品同步为商城商品并建立关联(GET /wine/app/product/:id)。上述小程序端能力为同一产品家族的移动延伸,其源码随小程序仓库独立管理与登记。

附录 C:与最新代码同步的功能增强(2026年7月24日)

本附录记录 2026年7月24日基于本机最新工作树(相对第十五轮提交的源码增量)核实的新增能力,确保说明书与源码一致。小程序端能力见附录 B.3,不在本软著源码快照内。

C.1 增强型运营仪表盘(策略 / 风险看板)

src/views/Dashboard.vue 新增多 Tab 运营看板(dashboard-tabs,默认 decision 决策视图),其中「策略仪表盘(strategy-dashboard)」实时呈现 activeRisk:包括风险描述(dashboard_risk_label / activeRisk.description)与系统建议的修复动作(dashboard_suggest_action / activeRisk.fix_action),并内置多语言文案(t('dashboard_risk_fix') 等)。该看板将风险识别结果直接投射到运营首页,使管理者在一次视图内完成「识别风险→查看建议→决策处理」的闭环。

C.2 端到端自动化采购预设(库存→采购→比价→PO)

server/boss-scheduler/profiles/automation-presets.yaml 新增预设:监控库存水位→低于阈值自动发起采购→自动询价→比价→生成采购订单(PO)→仅需老板一键确认。该链路复用平台的规则引擎与数字员工调度能力,把原先需多角色手工衔接的采购流程压缩为「系统自动跑、人工只拍板」的轻量审批,是自动化调度在供应链场景的落地示例。

C.3 内容引擎摘要生成与微信草稿发布

  • server/services/content-engine.js 在内容生成流水线新增 Step 7:调用 LLM 生成不超过 120 字的内容摘要(供前端自动生成缩略图的 Prompt),提升配图相关性。
  • server/routes/content-api.js 新增 uploadThumbFromUrl(将封面图 URL 下载后上传微信临时素材 material/add_material?type=thumb)与 publishToWechatDraft(发布至微信草稿箱),并支持 Pexels 动态匹配封面。内容与封面可随文章一键进入微信公众号草稿,打通「创作→配图→发布」链路。

C.4 Onboarding 与规则引擎自动激活联动

server/routes/onboarding.jsinit-system 阶段,依据所选行业模板的 business_modules,通过 ruleEngine.getModuleItemTypes 取得该行业涉及的 ItemType 集合,写入 data/rule_engine.db 并将 sciot_rules_v2 中对应 item_type_name 的规则置为 is_active=1UPDATE sciot_rules_v2 SET is_active=1 WHERE item_type_name IN (...) OR item_type_name IS NULL OR item_type_name=''),未激活规则则统一激活。由此新租户开通即预置并启用业务规则,与附录 B.1 的 Onboarding 向导形成「引导→规则自激活」闭环。

十八、桌面客户端(lragent-desktop)

本平台除网页端(浏览器访问)外,提供基于 Electron 的桌面客户端 lragent-desktop(package.json 名称 lragent-desktop,版本 1.1.0,入口 main.js)。桌面客户端是网页端的原生壳:启动后在应用窗口内加载本平台的 Web 界面,使用户获得“免浏览器、常驻桌面、可离线缓存”的使用体验,其全部业务功能、对象模型、数字员工与可视化能力均与原生网页端完全一致。

18.1 技术构成

  • 运行时:Electron(Chromium + Node.js),electron-app/main.js 创建 BrowserWindow 并加载网页端地址;electron/ 提供主进程 main.jspreload.jsrun.js 等支撑模块。
  • 内嵌服务:桌面客户端可在 userData/server-data 初始化并托管本地 bossagents 服务端(默认端口 3006),实现“单机即可运行”的轻量私有部署;离线资源缓存于 userData/offline-cache,弱网下仍可暂存数据。
  • 数据隔离:本地数据库初始化至 userData/databases,与浏览器端存储隔离,保障企业数据不下云。

18.2 与网页端的关系

桌面客户端不另写业务逻辑,而是复用网页端同一套 Vue3 + TypeScript 前端与 bossagents 后端,仅增加原生窗口、系统托盘、离线缓存与本地服务托管等桌面专属能力。如下为桌面客户端加载本平台 Web 界面(AI 智能体剧场)的实际运行示意:

图18-1 桌面客户端(lragent-desktop)运行本平台 Web 界面

18.3 适用场景

  • 常驻办公:将平台固定到任务栏 / Dock,免每次打开浏览器输入地址。
  • 弱网 / 离线:工厂车间、现场巡检等弱网环境,离线缓存保障关键操作不中断。
  • 私有化单机:配合私有部署,单台工控机即可承载平台,满足数据不出厂要求。

桌面客户端与网页端、微信小程序共同构成“三端协同”体系(详见小程序软著《左帮右臂工业智能体小程序软件》图4-1),三者共享同一后端、对象模型与数字员工能力。

著作权人信息

以下著作权人信息与中国版权保护中心登记申请表一致,供审查核对。

  • 软件全称: 博思智能体协同平台软件 V1.0
  • 开发完成日期: 2026年6月
  • 首次发表日期: 未发表
  • 著作权人: 北京左帮右臂人工智能技术有限公司
  • 著作权人类型: 法人(有限责任公司·自然人独资)
  • 证件类型: 营业执照
  • 统一社会信用代码: 91110114MAKJ1UC63J
  • 注册地址: 北京市昌平区东小口镇天通中苑二区21号楼1层103-2819(集群注册)
  • 联系人: 方云超
  • 联系电话: 18601921816
  • 电子邮箱: [email protected]
  • 邮政编码: 100010
← 返回案例列表
分享:
🤖 Try Now →
🤖
🎁