服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
252 lines
28 KiB
Markdown
252 lines
28 KiB
Markdown
# MCP 服务设计
|
||
|
||
状态:首版已实现。工具定义以 services/mcp_tools.go 为准,下文保留设计约定与实现边界。
|
||
|
||
当前实现:设置页启停及复制连接配置、本机 Streamable HTTP、共享服务实例、异步任务/状态查询、脚本预览应用、归档生命周期、冻结导入及普通/MOD 导出。界面准备屏障覆盖主窗口与脚本窗口,归档写入后同步标签内容和后端基线再释放屏障,避免将已应用变更误判为本地草稿。script_help 的 types 主题嵌入编辑器同源类型声明。连接无需鉴权,本机连接共享任务与计划。已实现标注搜索、详情和实际文件解释,见 [标注文档协议](MCP标注文档协议.md)。
|
||
|
||
当前限制:任务/请求去重历史仅保存在进程内,最多 256 条,重新启用清空;导入最多 10000 文件、单文件 8 MiB、总计 64 MiB;导出最多 10000 文件、内容 128 MiB。大导入快照保存在受限内存中,超限拒绝;尚未使用磁盘溢出存储。预览有效期 10 分钟,差异与日志有响应大小限制。生命周期和导出发布阶段不支持取消。
|
||
|
||
## 目标与首版范围
|
||
|
||
AI 客户端连接已运行的 pvfine,与桌面界面共享当前 PVF,支持打开、关闭、保存、另存为、普通文件/目录导入、普通导出、导出为 MOD,以及搜索、读取和修改归档内脚本。复用现有 `.pvf.js` 执行能力完成复杂查询和批量变更。
|
||
|
||
首版通过已运行的桌面应用管理归档生命周期及文件导入导出,AI 可直接传入本机归档路径和来源/目标路径,无需弹出文件选择框。用户已确认导入范围为普通文件和目录,不含 MOD 包导入。首版不提供无界面的独立归档进程、任意磁盘读写或系统命令工具。应用归档变更或导入后,可由 AI 调用保存/另存为,也可通过现有界面保存。
|
||
|
||
这里区分三种数据:归档内脚本、用于操作归档的 `.pvf.js` 程序,以及持久化在用户配置中的文件集。接口及结果必须说明变更的落点。
|
||
|
||
## 架构与传输
|
||
|
||
```text
|
||
AI MCP 客户端
|
||
│ Streamable HTTP /mcp
|
||
▼
|
||
internal/mcpserver:协议、身份校验、输入输出约束
|
||
▼
|
||
services:面向 AI 的应用门面、任务与预览管理
|
||
├── ArchiveService / EditorService:归档生命周期、搜索和读取
|
||
├── ScriptService:编译、运行、预览和应用
|
||
├── 导入门面 / ExportService:文件导入、普通导出及 MOD 导出
|
||
└── 共享 core:当前归档、版本、Overlay、事件
|
||
```
|
||
|
||
MCP Server 随桌面应用启动和停止,设置中提供启停开关,默认关闭。启用后使用本地 Streamable HTTP 端点,直接注入界面使用的服务实例;尤其必须共享同一个 ScriptService,避免独立实例绕过运行互斥。
|
||
|
||
使用官方 `github.com/modelcontextprotocol/go-sdk`。实现时选择与项目 Go 版本兼容的稳定版本,依靠 SDK 处理协议版本兼容。MCP 2026-07-28 与旧版本在生命周期和取消语义上存在差异,应用层统一使用任务 ID 和 context,避免自行固化某版协议细节。
|
||
|
||
固定首选端口可在设置中配置;端口占用时明确报错,界面显示实际端点和连接状态。多个应用实例使用不同端口,并通过 workspace 身份避免操作错归档。仅支持 stdio 的客户端可在后续通过轻量桥接进程连接已有服务,首版不增加桥接进程。
|
||
|
||
## 首版工具
|
||
|
||
workspace_info 与首次打开归档允许在空工作区调用。其余归档相关操作携带 `workspaceId`;读取返回 `revision`,执行、应用与生命周期写操作携带预期 revision。分页读取后 revision 变化时要求重读,避免混合不同版本的数据。生命周期另外使用 workspaceEpoch 防止空工作区检查与打开提交之间的竞争。
|
||
|
||
| 工具 | 输入要点 | 输出与语义 |
|
||
| --- | --- | --- |
|
||
| `pvf_workspace_info` | 无 | 是否打开、workspaceEpoch、workspaceId、源路径、归档格式、revision、源文件未保存状态、各窗口草稿状态、运行任务、能力版本 |
|
||
| `pvf_search` | workspaceId、路径/内容模式、query、scope、cursor、limit | 路径、命中摘要、revision、后续游标;限制单页与总响应大小 |
|
||
| `pvf_read_file` | workspaceId、path、行范围、expectedRevision | 脚本文本、总行数、revision、截断状态;路径使用归档内部路径 |
|
||
| `pvf_script_help` | topic | 分主题返回 API 类型、说明和示例;不依赖客户端支持 resources |
|
||
| `pvf_script_validate` | source | 语法诊断;不会验证业务含义或游戏效果 |
|
||
| `pvf_script_run` | workspaceId、expectedRevision、source、label、requestId | 立即返回 taskId;脚本在隔离事务中执行 |
|
||
| `pvf_script_status` | taskId、日志游标 | 状态、进度、分页日志、诊断;完成后返回 planId 和变更摘要 |
|
||
| `pvf_script_preview` | planId、filter、cursor、limit | 分页差异、changeKey、警告及依赖信息 |
|
||
| `pvf_script_apply` | planId、expectedRevision、changeKeys、requestId | 应用明确选中的变更,返回新 revision 和修改统计 |
|
||
| `pvf_script_cancel` | taskId | 取消指定任务;不能误取消界面或其他连接发起的任务 |
|
||
| `pvf_script_discard` | planId | 释放指定预览及其隔离事务 |
|
||
| `pvf_archive_open` | path、expectedWorkspaceEpoch、当前 workspaceId/revision(如有)、dirtyPolicy、requestId | 打开或切换归档;返回 taskId,完成后获取新 workspaceId、源路径、revision 与初始化状态 |
|
||
| `pvf_archive_close` | workspaceId、expectedRevision、dirtyPolicy、requestId | 关闭当前归档;返回 taskId,完成后报告空工作区及新 workspaceEpoch |
|
||
| `pvf_archive_save` | workspaceId、expectedRevision、requestId | 保存到源路径;返回 taskId,完成后报告 writtenPath、源文件未保存状态及文件标识 |
|
||
| `pvf_archive_save_as` | workspaceId、expectedRevision、path、overwrite(默认 false)、目标文件标识(覆盖时)、requestId | 生成副本并保留源路径;返回 taskId,完成后同时报告 writtenPath、sourcePath 与源文件未保存状态 |
|
||
| `pvf_task_status` | taskId | 查询生命周期、导入导出任务进度、结果和错误;与脚本任务共用底层任务管理,保留 script_status 的专用视图 |
|
||
| `pvf_import_prepare` | workspaceId、expectedRevision、sourcePaths、targetDir、mode、requestId | 冻结本地文件内容并校验映射,返回 taskId;完成后取得 importPlanId 与新增/覆盖摘要 |
|
||
| `pvf_import_preview` | importPlanId、cursor、limit | 分页返回源路径、归档目标路径、类型、大小、覆盖状态及差异 |
|
||
| `pvf_import_apply` | importPlanId、expectedRevision、selectedKeys、overwriteKeys、requestId | 将明确选择的冻结内容应用到工作区,不保存源 PVF;返回 taskId 和最终 revision |
|
||
| `pvf_import_discard` | importPlanId | 释放冻结内容与预览 |
|
||
| `pvf_export_formats` | 无 | 格式 ID、读写能力与支持操作,可写 MOD 格式包括 110USextend 和 115CN-mod |
|
||
| `pvf_export_prepare` | workspaceId、expectedRevision、source、scopes、mode、format、name、version、modId、includeDependencies、来源附加参数、requestId | 准备普通/MOD 导出快照,115CN-mod 必须提供唯一 modId;返回 taskId,完成后取得 exportPlanId、总数与警告 |
|
||
| `pvf_export_preview` | exportPlanId、cursor、limit | 分页输出树、entryKey、依赖提示、跳过项与警告 |
|
||
| `pvf_export_execute` | exportPlanId、outputParent、outputName(direct 必填)、selectedEntryKeys、acknowledgedWarningIds、requestId | 发布普通文件目录或 MOD 目录;返回 taskId,完成后返回实际输出路径和统计 |
|
||
| `pvf_export_discard` | exportPlanId | 释放导出快照 |
|
||
|
||
`pvf_script_run` 同时支持只读查询:脚本用 `pvf.log` 输出查询结果,没有变更时不保留无用预览。大型查询优先使用搜索/读取工具,避免把所有结果堆入日志。首版不新增脚本 return/结果集语言语义。
|
||
|
||
通过 `pvf_script_help` 与可选 MCP resources 暴露相同的分主题文档,例如 `pvfine://script-api/sections`。文档应和 `frontend/src/script-api.d.ts`、脚本能力版本一起维护,并提供一致性检查。归档路径和内容是数据,不作为服务器指令执行。
|
||
|
||
`.pvf.js` 程序库的列出、读取和保存可后续映射现有 ListScripts/LoadScript/SaveScript;首版通过 source 执行,不默认改写界面中尚未保存的脚本源码。
|
||
|
||
## 典型调用流程
|
||
|
||
1. AI 查询工作区;需要时调用 archive_open 打开指定路径,获取 workspaceId 和 revision,搜索候选文件并读取样本。
|
||
2. 按需查询脚本 API,生成 source,验证语法。
|
||
3. 提交执行任务;服务在接纳任务时记录任务身份和预期 revision。
|
||
4. AI 查询进度与运行结果,读取差异及警告。
|
||
5. AI 按明确的 changeKeys 应用结果;revision 已变化则拒绝并要求重新运行。
|
||
6. UI 刷新受影响文件并显示未保存状态,AI 调用 archive_save/archive_save_as,或用户使用原有保存入口写回 PVF。
|
||
7. 结束后可调用 archive_close;有未保存内容时按显式 dirtyPolicy 处理。
|
||
|
||
导入分支:import_prepare → import_preview → import_apply → 按需保存。导出分支:export_prepare(mode=direct/mod)→ export_preview → export_execute。两种导出都不要求先保存 PVF。
|
||
|
||
例如“把某目录价格低于 10 的装备提高到 10”,可直接复用脚本工作区的 `pvf.glob → file.parse → section.set → file.write`。工具描述应提示保留数值类型、显式处理重复 section,并检查字符串表与列表依赖。
|
||
|
||
## 归档生命周期
|
||
|
||
### 打开与切换
|
||
|
||
`pvf_archive_open` 接受本机绝对文件路径,不弹对话框,不自动猜测路径。空工作区使用 workspace_info 返回的 expectedWorkspaceEpoch;已有归档时还必须匹配 workspaceId 和 expectedRevision。同一路径重新打开也视为新工作区,不能绕过未保存检查。
|
||
|
||
打开前取得生命周期操作占用权,在候选归档上完成解析和必要索引准备,成功后才替换当前工作区;失败保留原归档、Overlay 和草稿。切换提交前再次校验工作区身份、revision 和草稿状态,防止解析期间 UI 编辑造成覆盖。后台版本恢复和索引任务按工作区代次过滤,不能将旧归档结果装入新工作区。
|
||
|
||
成功返回新的 workspaceId、workspaceEpoch、revision,以及 indexStatus/versionStatus。打开完成不等于后台索引和版本恢复完成;未就绪操作返回 NOT_READY,并说明可查询的状态。
|
||
|
||
### 未保存内容与关闭策略
|
||
|
||
open/close 的 dirtyPolicy 支持 `reject`(默认)、`save`、`discard`,只处理当前归档的已同步 Overlay:
|
||
|
||
| 策略 | 当前源文件有未保存变更时的行为 |
|
||
| --- | --- |
|
||
| reject | 返回 UNSAVED_CHANGES,保留当前工作区 |
|
||
| save | 先保存到当前源文件,成功后再切换/关闭;保存失败则保留当前工作区 |
|
||
| discard | 明确丢弃当前 Overlay 后切换/关闭;仍需匹配身份和 revision |
|
||
|
||
这些策略不隐式应用脚本预览,不清除前端未提交草稿。存在前端草稿时统一返回 UI_DRAFT_PENDING,要求在现有编辑器提交或处理草稿后重试。`discard` 不能绕过草稿保护。独立脚本窗口的未保存 `.pvf.js` 源码属于程序草稿,切换/关闭归档应保留,不将其计入归档 Overlay。
|
||
|
||
运行中的脚本或已有待处理预览分别返回 TASK_BUSY / PLAN_PENDING,先通过既有工具取消、应用或丢弃,再操作归档;不能通过 close/open 隐式终止其他调用方的任务。生命周期操作互斥,同时阻止 UI/MCP 开始新的归档写入或脚本任务。
|
||
|
||
close 成功释放归档资源、索引与版本会话,并按现有语义清理该源路径的自动备份;桌面应用与 MCP Server 继续运行,允许再次打开。workspaceEpoch 更新,旧工作区句柄失效。同一 requestId 的重试返回原关闭结果;新请求引用旧身份返回 STALE_WORKSPACE。
|
||
|
||
### 保存
|
||
|
||
`pvf_archive_save` 保存后端已提交的当前 revision 到 sourcePath。前端仍有文本或 GUI 草稿时返回 UI_DRAFT_PENDING,不返回遗漏草稿的“保存成功”。没有源路径时返回 SAVE_AS_REQUIRED。保存使用现有原子写入、源文件备份、版本记录和索引更新流程。
|
||
|
||
保存前比较磁盘文件与打开或上次成功保存时的文件标识;不一致返回 SOURCE_CHANGED,禁止覆盖外部修改。文件标识包含必要的文件身份和内容校验,不能只依赖 mtime。文件替换前再次检查;对于不遵守应用锁的外部写入,普通文件系统检查不等于跨进程事务,结果与文档需说明这一边界。
|
||
|
||
成功返回 writtenPath、sourcePath、savedRevision、当前 revision、源文件未保存状态和更新后的文件标识。必须区分磁盘替换是否完成和后续版本/缓存维护是否完成:若文件已写入但后续维护失败,返回 diskCommitted=true 及告警,不能将其描述为“未保存”并盲目重试。
|
||
|
||
### 另存为:只生成副本
|
||
|
||
用户已确认:`pvf_archive_save_as` 将当前内容写入指定目标,sourcePath 保持不变,后续 save 仍写原文件,workspaceId 不因副本生成而变化。
|
||
|
||
生成副本不代表原文件已保存:保留相对源文件的未保存状态、待同步变更和自动备份,不将当前版本标记为已写回源文件。若当前内容本来就与源文件一致,仍保持无未保存变更。返回 writtenPath、sourcePath、snapshotRevision、sourceDirty,避免客户端将副本路径误认为当前源路径。
|
||
|
||
当前 SaveAsDialog 会清理 mutations/自动备份并重置前端 dirty 基线,不能原样复用为“生成副本”的完整语义。应提取不弹对话框的 SaveCopy(path, options),隔离序列化过程对工作区的副作用;UI 另存为也复用该门面并保留源文件未保存状态。
|
||
|
||
目标路径必须是绝对路径,父目录须已存在;目标与源文件等价时返回 SAME_AS_SOURCE,要求调用 save。按规范化路径、符号链接和文件身份识别同一文件,覆盖目标不允许是目录。
|
||
|
||
默认 overwrite=false,目标已存在返回 TARGET_EXISTS;显式 overwrite=true 时还需提供此前观察到的目标文件标识,并在提交前复核,目标变化返回 TARGET_CHANGED。冲突结果可返回非敏感文件标识供下一次明确覆盖请求使用。不得以“先判断不存在再普通 rename”实现禁止覆盖,须使用平台支持的原子禁止覆盖提交方式。
|
||
|
||
### 前端同步与任务边界
|
||
|
||
后端增加统一生命周期门面,UI 打开/关闭/保存/另存为与 MCP 共用互斥和状态检查。原 ArchiveService.Close 会直接丢弃修改,必须由门面保护;不能直接暴露为工具。
|
||
|
||
前端所有归档编辑视图上报草稿状态与代次;生命周期操作通过带 operationId 的 prepare/ack 检查各窗口并短暂冻结编辑,收到全部有效响应后复核 revision,再进入提交。超时或窗口状态未知返回 UI_NOT_READY,不能按“无草稿”继续。纯前端 dirty 布尔值的延迟上报不足以消除保存竞争。
|
||
|
||
打开、保存和另存为可能耗时,使用 taskId 与 task_status 返回阶段进度,避免长时间阻塞单次 MCP 调用。首版不开放生命周期任务取消:文件替换开始后不能假定可撤销;退出/停用服务等待不可中断提交完成,任务始终报告最终磁盘结果。
|
||
|
||
沿用 archive:opened/closed/saved,并增加副本生成事件 archive:copy-saved。事件携带 workspaceId、revision、来源、operationId 和 writtenPath;副本事件不能触发清除原文件 dirty 的逻辑。UI 可在重连时查询后端状态,事件不作为唯一事实来源。
|
||
|
||
## 导入、导出与 MOD
|
||
|
||
### 普通文件与目录导入
|
||
|
||
sourcePaths 使用本机绝对路径,targetDir 使用归档内部路径,空串表示归档根。单文件按文件名映射,目录保留被选目录名称和内部层级,沿用现有 collectImportFiles 行为。例如选择 `/tmp/items/equipment`,导入到根后得到 `equipment/...`。多个来源映射到同一路径时拒绝并列明冲突,不依赖遍历顺序决定覆盖。
|
||
|
||
mode 支持现有 text 与 raw:text 按现有 UTF-8 校验及归档文本编码规则写入,raw 按原始数据导入;预览报告实际类型。拒绝符号链接来源、特殊文件及非法归档路径;目录遍历设置文件数量、单文件与总字节数上限,沿用项目对 secrets 的禁止读取要求。
|
||
|
||
prepare 只生成隔离预览,展示新增/覆盖、目标路径、文本差异或二进制大小/摘要。现有 PreviewImport 不保留内容,ImportFiles 会重新读取磁盘;需提取保留冻结字节和来源摘要的导入计划,apply 消费相同快照,不能重新读取变化后的来源。大快照可使用受控临时存储,按 TTL、丢弃、归档切换及应用退出清理;分页替代现有最多 500 条后截断的预览形式。
|
||
|
||
apply 明确传入 selectedKeys;覆盖已有条目还须在 overwriteKeys 中逐项指定,默认不覆盖。先检查全部目标与依赖,再通过隔离 stage 一次提交所选内容;失败不部分写入归档。提交后的索引或版本维护错误需区分 workspaceCommitted,避免重复导入。成功后更新 revision、树、编辑器与版本状态,源 PVF 保持未保存。
|
||
|
||
导入不自动删除来源中缺失的归档文件,也不自动注册 `.lst` 或重写字符串表关系;关联修改需显式导入关联文件或使用脚本处理。工具说明需明确文本与 raw 模式的区别。
|
||
|
||
导入应用与脚本应用共用写入门面、revision 检查和前端草稿保护。已有运行任务或待处理写入计划时拒绝开始新导入;导入计划与脚本计划均属于待处理写入计划,open/close/save/save_as 的 PLAN_PENDING 检查须同时覆盖二者。
|
||
|
||
### 统一导出与来源
|
||
|
||
普通导出和导出为 MOD 共享 prepare/preview/execute,通过 mode=direct 或 mod 区分;MOD 格式通过 export_formats 发现。首版来源包含 selection(指定文件/目录,文件集由调用方展开)、memory(未启用版本控制的内存改动)、working(相对 HEAD 的工作区变更)和 commit(指定提交)。来源附加参数包括 commitID;首版不暴露 reverse 或 archive-diff 来源。
|
||
|
||
direct 沿用现有文本/二进制导出规则,不宣称所有类型都能无损往返;无法表达的类型须报告,不能静默导出空文件。全量导出可使用 selection 的归档根 scope,仍受文件数量与总字节上限约束。现有 archive-diff 来源在 AI 能发现/创建比较结果后再接入,首版不要求提供无法发现的 diffID。
|
||
|
||
导出读取已提交的后端内容及 Overlay,不应用前端草稿、脚本/导入预览,不创建提交,也不保存 PVF。存在归档文本或 GUI 草稿时返回 UI_DRAFT_PENDING。快照记录 workspaceId、revision、版本会话、HEAD 和来源身份;执行时失效返回 STALE_EXPORT,要求重新准备。
|
||
|
||
includeDependencies 必须显式声明。预览返回依赖数量、缺失/跳过原因并支持完整分页。selectedEntryKeys 对应预览中的输出文件路径,由服务映射到逻辑内容项,MOD 元数据由服务重建,不能作为普通内容排除。复用现有从冻结快照排除内容的语义,不重新计算或自动补回依赖;最终选择若移除依赖,服务返回依赖警告,执行须显式匹配 acknowledgedWarningIds。
|
||
|
||
### MOD 格式边界
|
||
|
||
首版复用 internal/mod 注册的 110USextend Writer,输出为 `<outputParent>/<name>-<version>/`,含 pack.json 和所需的 pvf/、merge/,不是压缩成单个 .mod 文件。
|
||
|
||
115CN-mod 扩展输出 `<outputParent>/<name>-<version>.zip`,包含根级 mod.json 和 pvf/ 载荷;`export_prepare` 的 `modId` 为稳定唯一 MOD ID,发布同包更新时复用,不能用显示名称替代。仍复用预览、筛选、依赖警告确认和拒绝覆盖的发布流程。MCP 不安装客户端资源,MOD 包导入使用 GUI。
|
||
|
||
name、version 复用现有校验,版本省略或留空默认 1.0。普通脚本放入 pvf/;`.lst`、`.str` 按格式写入 merge/。工作区/提交来源有准确基线时输出新增或修改的表项;无基线的整表按现有格式规则跳过并返回警告,不回退为整表覆盖。
|
||
|
||
该格式不支持文件和表项删除,返回 skippedDeletes 与明细;纯删除或筛选后无可应用内容时拒绝输出。direct 的删除项输出修改前内容,这属于文件导出语义,不表示生成删除补丁。不能把导出成功描述为支持全部变更或已验证游戏效果。
|
||
|
||
跳过、解析失败、依赖不完整项赋予稳定 warningId,execute 需显式回传已查看的 ID;非法路径和无法表达的内容仍硬失败。首版不实现 MOD Reader,也不把 MOD 目录的 pack.json、merge/ 等包装内容当成普通归档资源自动导入。
|
||
|
||
### 发布、计划与任务
|
||
|
||
outputParent 使用本机绝对路径。首版 direct 显式指定 outputName,在父目录中新建专属输出目录;MOD 使用 name 作为目录名。同名目录存在一律返回 TARGET_EXISTS,不合并或覆盖。两种模式均先写入同父目录下的临时目录,再原子地以禁止覆盖方式发布,失败只清理本次临时目录。需调整现有 writeDirectDirectory 直接逐文件写入目标的入口,避免半成品和覆盖旧文件。
|
||
|
||
复用安全路径校验,检查符号链接、大小写及平台文件名冲突。结果返回 outputPath、文件数、依赖数、来源 revision、格式和警告;错误区分是否已经发布。prepare/apply/execute 按 requestId 去重,发布后丢失响应的重试返回同一结果,不能重复生成目录或误报目录已存在。
|
||
|
||
当前 ExportService 仅保留一份短期预览,新 Prepare 会替换旧预览。首版保持数量限制,但纳入共享占用与来源身份;UI 与 MCP 不得相互替换导出预览,已有计划返回 EXPORT_PLAN_PENDING。导入、导出和脚本计划使用不同 ID 命名空间,共用 task_status、TTL;本机连接共享计划。
|
||
|
||
导出预览存在期间允许编辑,但编辑使计划过期;open/close 需先显式释放导出计划。执行任务期间阻止切换/关闭及冲突写入,提交/发布阶段不开放取消。UI 显示 AI 来源、进度和最终目录,导入后刷新受影响标签页及目录树。退出/停用时等待不可中断提交,清理其余预览与临时存储。
|
||
|
||
## 现有实现需要调整的边界
|
||
|
||
### 单运行与单预览
|
||
|
||
当前 ScriptService 按实例互斥运行,core 只保存一个 scriptPlan;Run 会丢弃旧预览。首版保持单任务、单预览,新增统一的占用状态,覆盖 UI 和 MCP 两个入口:已有待处理预览时返回 PLAN_PENDING,要求先应用或丢弃,不能静默替换。
|
||
|
||
预览记录来源与调用方身份。本机 AI 连接共享任务和预览,UI 可以查看、应用或丢弃 AI 预览;若用户在 UI 处理了计划,AI 后续查询得到对应终态。占用检查和任务接纳必须原子执行,不能只在 MCP handler 中检查一次。
|
||
|
||
### 工作区一致性
|
||
|
||
继续使用现有 archive 身份和 batchRevision 校验,并增加对外不透明 workspaceId;切换或重新打开归档必须更换身份。传入路径在当前身份下解析,避免长期暴露会因增删文件而改变的 index。
|
||
|
||
界面和 AI 均可能编辑工作区。执行开始、运行完成和应用提交分别校验身份与 revision,冲突返回 STALE_WORKSPACE 或 STALE_PLAN,不覆盖最新编辑。多文件应用继续通过现有事务和依赖校验完成,不在 MCP 层循环调用 SetText。
|
||
|
||
### 应用副作用
|
||
|
||
归档修改应用到 Overlay,沿用版本记录与索引失效逻辑。字符串表关联修改必须一起选中或由服务报告缺失依赖,不能静默部分成功。
|
||
|
||
现有文件集脚本会在 Apply 时写入用户配置。首版 MCP 明确拒绝包含文件集变更的计划,返回 UNSUPPORTED_CHANGE,避免将其描述为“仅修改未保存的 PVF”。未来开放时应提供独立能力与副作用说明。
|
||
|
||
### UI 同步
|
||
|
||
目前运行结果和 planId 主要保存在前端 Pinia 中,仅复用后端日志事件不足以让界面接管 MCP 运行。新增后端任务状态查询,以及 started/completed/preview-created/applied/discarded 状态事件,事件携带 taskId、来源和 workspaceId。
|
||
|
||
前端打开脚本工作区时先拉取后端状态,事件用于后续增量刷新;AI 生成的源码作为独立任务展示,不能覆盖用户当前未保存的源码。日志按游标和字节数截断,明确返回 hasMore/truncated。
|
||
|
||
### 超时、重试与释放
|
||
|
||
沿用现有脚本源码 1 MiB、执行 5 分钟和日志 5000 条限制,再为日志单条大小、总字节、响应大小、预览寿命和任务历史设置上限。Goja 的 context 中断不是内存硬隔离,应限制并行数并验证大分配场景;需要严格内存上限时再考虑执行子进程。
|
||
|
||
run/apply、open/close/save/save_as,以及导入导出的 prepare/apply/execute 使用调用方范围内的 requestId 去重。同一 ID 和相同参数返回已有结果,相同 ID 配不同参数返回冲突;保存请求摘要及有限期结果,避免网络重试重复应用、保存或发布。去重记录跨工作区切换保留,重试先查去重记录再验证旧工作区身份。应用重启后无法确定的提交结果要求核对磁盘文件,不直接重放。
|
||
|
||
取消按 MCP 所属 taskId 校验;MCP 请求返回后后台脚本任务仍运行,显式 script_cancel 工具负责停止。MCP open/close 先要求处理已有脚本任务和预览;应用退出和服务停用时取消可取消任务、等待不可中断生命周期提交并释放预览。连接中断不立即丢弃预览,按可配置的有限 TTL 回收。
|
||
|
||
## 本地访问控制
|
||
|
||
只绑定 loopback,校验 Host 和 Origin。首版不做鉴权,连接配置只提供服务 URL;本机客户端共享 MCP 任务和计划。关闭服务时停止接受请求。
|
||
|
||
不把 EditorService 或整个 Wails API 自动注册为 MCP 工具。所有能力使用明确工具列表和参数 schema;返回业务错误码、简短描述和可执行的恢复建议。
|
||
|
||
## 实施顺序与验收
|
||
|
||
1. 服务层增加共享任务管理、预览占用和调用方身份,提取归档生命周期门面、SaveCopy、冻结导入计划及无对话框导出接口;保持现有 UI 可用。
|
||
2. 加入 MCP Server 和工具,复用现有服务;补充身份、revision、workspaceEpoch、分页、去重与资源释放逻辑。
|
||
3. 在现有设置末尾增加 MCP 配置区,并接通 UI 状态查询与事件。
|
||
4. 接通各窗口草稿 prepare/ack 与生命周期事件,验证真实 MCP 客户端的发现、打开、搜索、读取、执行、预览、应用、导入、保存/生成副本、普通导出、MOD 导出及关闭全链路。
|
||
|
||
针对性测试覆盖:UI/MCP 并发互斥、待处理预览保护、归档切换、编辑冲突、调用方隔离、重复请求、取消与超时、过期回收、字符串表依赖、文件集变更拒绝、分页截断、Host/Origin 拒绝。生命周期另覆盖:空工作区打开竞争、打开失败保留原状态、dirtyPolicy 三种分支、各窗口草稿与响应超时、外部源文件修改、目标覆盖竞争、路径别名、保存失败重试、磁盘成功后维护失败、生成副本保留源路径/dirty/自动备份、切换后的请求去重及旧任务结果隔离。运行 `go test ./...` 和前端构建;真实 PVF 回归按项目惯例通过 PVF_TESTFILE 启用。
|
||
|
||
导入导出另覆盖:来源文件在预览后变化、目录映射冲突、部分选择与覆盖许可、源路径与目标路径混用、快照限额与清理、提交后的维护失败、导出计划互斥和过期、文件筛选后的依赖警告、同名目录竞争、输出失败清理、发布后的重复请求,以及 MOD 无基线表、删除跳过、空包和最终元数据。复用现有 import/export/mod 测试,并运行前端 `npm test` 验证选择和预览交互。
|
||
|
||
## 官方参考
|
||
|
||
标注说明的后续只读扩展见 [MCP 标注文档查询协议](MCP标注文档协议.md),设计了规则搜索、规则详情和实际文件解释三个工具。该扩展当前仅完成协议设计,尚未注册到 MCP 服务。
|
||
|
||
- MCP 传输规范:https://modelcontextprotocol.io/specification/2026-07-28/basic/transports
|
||
- 官方 Go SDK:https://github.com/modelcontextprotocol/go-sdk
|
||
- 项目现有 MOD 语义:[MOD.md](MOD.md)
|