1186 lines
51 KiB
Markdown
1186 lines
51 KiB
Markdown
# RA3 Mod XML AI Agent 接入计划与进度追踪
|
||
|
||
> 状态:实施中
|
||
> 创建时间:2026-09-09
|
||
> 目标:让 AI Agent / 其他工具能够方便、可靠地使用 RA3 Mod XML 扩展构建的语义索引。
|
||
|
||
---
|
||
|
||
## 进度追踪
|
||
|
||
| 阶段 | 内容 | 状态 |
|
||
|---|---|---|
|
||
| Phase 0 | 计划与进度文件 | ✅ 完成 |
|
||
| Phase 1 | 外部快照格式与导出命令 | ✅ 完成 |
|
||
| Phase 2 | 查询核心与 CLI | ✅ 完成 |
|
||
| Phase 3 | MCP Server | ✅ 完成(基础 stdio MCP) |
|
||
| Phase 4 | MCP 配置助手 / 稳定 launcher | ✅ 完成(launcher + 启用命令 + Claude/Cursor 配置写入 + 复制配置) |
|
||
| Phase 5 | Agent Skill 安装器 | ✅ 完成(默认安装 + 多位置选择 + 安装记录 + 卸载/同步函数) |
|
||
| Phase 6 | Live 查询与快照合并策略 | ✅ 完成(本地只读 HTTP live server + endpoint 文件 + MCP 在线优先/离线兜底 + final 索引静默 5 秒自动快照) |
|
||
| Phase 7 | 测试、文档、发布 | 🟡 部分完成(README + 计划文档已更新;全部 31 个 test/*.test.mjs 逐个直接运行通过;已用直接 esbuild CLI 验证 dist 构建与 MCP smoke test;`node --test` 受沙箱限制,尚未 VSIX 打包/发布) |
|
||
|
||
### 二期进度(2026-09-10)
|
||
|
||
| 阶段 | 内容 | 状态 |
|
||
|---|---|---|
|
||
| 二期 A1 | live 响应携带 `projectDir`,MCP 侧校验 | ✅ 完成 |
|
||
| 二期 A2 | endpoint 按项目分文件(保留全局兜底) | ✅ 完成 |
|
||
| 二期 A3 | live server 支持 `?project=` 路由(不再只看活动编辑器) | ✅ 完成 |
|
||
| 二期 A4 | 关闭窗口只清理自己写的 endpoint | ✅ 完成 |
|
||
| 二期 A5 | `processId` 存活校验 | ✅ 完成 |
|
||
| 二期 A6 | live 失败负缓存 | ✅ 完成 |
|
||
| 二期 A7 | `snapshotBaseName` 改为 sha1 前缀 + 可读 slug | ✅ 完成 |
|
||
| 二期 B1 | Skill 适用范围段落(正/负信号 + `get_status` 探针) | ✅ 完成 |
|
||
| 二期 C1 | `get_asset_references`:有界、带 provenance 的正向引用查询 | ✅ 完成 |
|
||
| 二期 C2 | 本地 HTTP `/get_asset_references` 与 MCP 工具接入 | ✅ 完成 |
|
||
| 二期 C3 | 二期测试与文档 | ✅ 完成(264/264 测试通过) |
|
||
|
||
### 三期进度(2026-09-10)
|
||
|
||
| 阶段 | 内容 | 状态 |
|
||
|---|---|---|
|
||
| 三期 0 | 讨论结论与进度落档 | ✅ 完成 |
|
||
| 三期 1 | 运行时解析:无 Node 时用 VS Code Electron(`ELECTRON_RUN_AS_NODE`) | ✅ 完成 |
|
||
| 三期 2 | 抽出 `src/agent/liveClient.ts`,MCP 与 CLI 共用转发/校验/负缓存 | ✅ 完成 |
|
||
| 三期 3 | `instances/` 注册表 + 跨实例剪枝 + 只读 `index.json` 发现面 | ✅ 完成 |
|
||
| 三期 4 | CLI 瘦身:live 优先、快照兜底、cwd 项目识别、补 `projects`/`get_asset_references` | ✅ 完成 |
|
||
| 三期 5 | 三期测试与文档 | ✅ 完成(302 用例,299 通过 / 3 因沙箱跳过) |
|
||
| 三期 6 | (可选)TCP → 命名管道 / UDS 传输 | ⬜ 未开始 |
|
||
|
||
---
|
||
|
||
## 1. 背景与目标
|
||
|
||
当前 RA3 Mod XML 扩展已经能构建大型项目的语义索引,包括:
|
||
|
||
- asset 定义:`type / id / file / line / origin / stream`
|
||
- `$DEFINE`
|
||
- `<Include>` / `xi:include` 关系
|
||
- 语义引用和反向引用
|
||
- manifest 资产
|
||
- `.w3x` art 资产
|
||
|
||
这些能力目前主要服务于 VS Code 编辑器内部。
|
||
本计划的目标是:
|
||
|
||
1. 让 AI Agent、脚本和其他工具能够方便、可靠地使用该索引;
|
||
2. 不需要用户注册全局 CLI / 修改 PATH;
|
||
3. 安装/更新扩展后能通过一次性设置完成接入;
|
||
4. 在用户持续编辑代码时,索引查询仍然足够实时且不会造成磁盘写放大;
|
||
5. 为 AI Agent 提供工具 + 引导 Skill,让它知道何时、如何调用索引。
|
||
|
||
---
|
||
|
||
## 2. 核心设计决策
|
||
|
||
### 2.1 不采用“全局 CLI + PATH”作为主入口
|
||
|
||
CLI 可以作为高级/调试工具保留,但不是默认路径。
|
||
|
||
### 2.2 以 MCP Server 作为 AI Agent 主接口
|
||
|
||
MCP Server 使用本地 stdio 启动,通过绝对路径配置即可,不需要 PATH。
|
||
|
||
### 2.3 内存索引为主,磁盘快照为辅
|
||
|
||
- 实时查询尽量走 VS Code 扩展内存中的 `ModIndex`;
|
||
- 磁盘只保存低频、合并后的“当前稳定快照”作为离线兜底;
|
||
- 避免用户连续编辑保存时产生大量磁盘 IO。
|
||
|
||
### 2.4 Agent Skill 是可选增强层
|
||
|
||
MCP 注册解决“工具可用”,Skill 解决“Agent 知道怎么用好工具”。
|
||
Skill 不作为启用 MCP 的强制条件。
|
||
|
||
### 2.5 所有查询结果都携带索引状态
|
||
|
||
外部接口必须返回:索引是否存在、是否正在构建、是否完整、是否 stale、数据版本/时间。
|
||
|
||
---
|
||
|
||
## 3. 目标架构
|
||
|
||
```text
|
||
VS Code 扩展
|
||
│
|
||
├── 内存 ModIndex
|
||
│ │
|
||
│ ├── 本地只读查询服务(可选,热数据)
|
||
│ └── 低频快照导出(冷数据)
|
||
│
|
||
├── MCP 配置助手
|
||
│ └── 写入 AI 客户端配置 + 稳定 launcher
|
||
│
|
||
└── Agent Skill 安装器
|
||
└── 安装到 ~/.agents/skills 等位置
|
||
|
||
AI Agent / 外部工具
|
||
│
|
||
├── MCP Server(推荐)
|
||
│ ├── 扩展在线 → 查询内存/本地服务
|
||
│ └── 扩展离线 → 读取最近稳定快照
|
||
│
|
||
├── CLI(可选)
|
||
└── HTTP/JSON-RPC(可选)
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 组件设计
|
||
|
||
### 4.1 外部快照格式
|
||
|
||
定义一份稳定、公开的快照 schema,不直接暴露内部 workspaceStorage 缓存。
|
||
|
||
```json
|
||
{
|
||
"schemaVersion": 1,
|
||
"project": "D:/Mods/CoronaMod/mods/mods/corona",
|
||
"generatedAt": "...",
|
||
"buildId": 128,
|
||
"phase": "art",
|
||
"complete": true,
|
||
"stale": false,
|
||
"stats": {
|
||
"assetCount": 38304,
|
||
"referenceCount": 97862,
|
||
"defineCount": 240,
|
||
"streamCount": 12
|
||
},
|
||
"assets": [
|
||
{
|
||
"type": "GameObject",
|
||
"id": "...",
|
||
"file": "Data/...",
|
||
"line": 10,
|
||
"origin": "project",
|
||
"stream": "static",
|
||
"viaInstance": false
|
||
}
|
||
],
|
||
"defines": [],
|
||
"references": [],
|
||
"includeGraph": [],
|
||
"sourceCandidates": []
|
||
}
|
||
```
|
||
|
||
生成方式:
|
||
|
||
- 从 `ModIndex` 导出;
|
||
- 使用数组而不是 JS Map;
|
||
- 原子写入:`temp + rename`;
|
||
- 同一项目只维护 `current` 快照,不保留每次历史版本;
|
||
- 可选维护 `previous` 用于构建期间回退。
|
||
|
||
### 4.2 MCP Server
|
||
|
||
MCP Server 不直接持有完整索引,而是作为查询代理:
|
||
|
||
1. 扩展在线时,转发到 VS Code 扩展的本地只读查询服务;
|
||
2. 扩展离线时,读取最近一次稳定快照;
|
||
3. 所有工具返回结果时附带索引状态。
|
||
|
||
建议 MCP 工具集:
|
||
|
||
```text
|
||
get_status()
|
||
list_projects()
|
||
find_asset(id, type?)
|
||
find_references(id, type?)
|
||
list_assets_by_type(type, prefix?, limit?)
|
||
get_definition(type, id)
|
||
resolve_include(source)
|
||
is_file_active(path)
|
||
find_define(name)
|
||
get_usage_guide()
|
||
```
|
||
|
||
### 4.3 稳定 launcher 与 MCP 配置助手
|
||
|
||
扩展安装路径会随版本变化,所以不能直接写死在 MCP 配置里。
|
||
|
||
稳定入口:
|
||
|
||
```text
|
||
~/.ra3modxml/
|
||
ra3-mod-xml-mcp.cmd
|
||
projects.json
|
||
snapshots/
|
||
current.json.gz
|
||
meta.json
|
||
skill-install.json
|
||
```
|
||
|
||
MCP 配置示例:
|
||
|
||
```json
|
||
{
|
||
"mcpServers": {
|
||
"ra3-mod-xml": {
|
||
"command": "C:\\Users\\lanyi\\.ra3modxml\\ra3-mod-xml-mcp.cmd",
|
||
"args": ["--project", "D:\\Mods\\CoronaMod\\mods\\mods\\corona"]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
扩展每次激活/升级时刷新 launcher 内容,指向当前扩展安装目录。
|
||
这样 MCP 配置只需要写一次,升级不断。
|
||
|
||
### 4.4 Agent Skill
|
||
|
||
默认安装:
|
||
|
||
```text
|
||
~/.agents/skills/ra3-mod-xml/
|
||
├── SKILL.md
|
||
└── references/
|
||
└── query-guide.md
|
||
```
|
||
|
||
Skill 内容原则:
|
||
|
||
- 只描述功能本身;
|
||
- 说明“什么情况下该用这个 Skill”以及“怎么用”;
|
||
- 不引用各个工作区里的具体文档,例如 `docs/codebase-navigation-guide.md`,避免注意力噪音;
|
||
- 保持通用,不绑定某个 mod。
|
||
|
||
Skill 内容建议包括:
|
||
|
||
- 索引覆盖范围和状态含义;
|
||
- MCP 工具用途;
|
||
- 推荐查询工作流:
|
||
- 从 ID 找定义 → `find_asset`
|
||
- 找所有引用 → `find_references`
|
||
- 确认文件是否 active → `is_file_active`
|
||
- 追继承链 → `find_references` + `find_asset`
|
||
- 找未引用资产 → `unreferenced`
|
||
- 使用规则:
|
||
- 优先查询索引,而不是全项目 `rg`;
|
||
- 查询尽量带 type,避免同名不同类型混淆;
|
||
- 不要把整个索引快照读进上下文;
|
||
- 如果索引状态不是 ready/stale,应查询状态或重试。
|
||
|
||
### 4.5 索引状态与一致性
|
||
|
||
定义索引状态:
|
||
|
||
| 状态 | 含义 |
|
||
|---|---|
|
||
| `no_index` | 从未构建 |
|
||
| `building` | 正在构建/重建 |
|
||
| `ready_xml` | XML phase 已发布,art 未完成 |
|
||
| `ready` | 完整 final 索引 |
|
||
| `stale` | 构建期间有文件变化 |
|
||
| `error` | 构建失败但保留 last good |
|
||
|
||
查询接口支持两种模式:
|
||
|
||
- 默认 / eventual:有可用快照就返回,同时附带 `stale/incomplete` 标记;
|
||
- 严格 / consistent:调用方传 `wait_for_index=true` / `allow_stale=false`,等待新 final 索引或超时。
|
||
|
||
### 4.6 持久化策略
|
||
|
||
- 热数据:内存 `ModIndex`;
|
||
- 实时查询:本地查询服务 / MCP 转发;
|
||
- 冷数据:低频快照,例如:
|
||
- 用户手动导出;
|
||
- 索引稳定一段时间后自动写一次;
|
||
- 扩展退出前写一次(尽力而为);
|
||
- 不每次 rebuild 都写全量快照。
|
||
|
||
### 4.7 用户交互流程
|
||
|
||
安装/更新扩展后:
|
||
|
||
1. 首次索引完成;
|
||
2. 如果尚未启用且未被用户忽略,弹出提示:
|
||
|
||
```text
|
||
RA3 Mod XML 索引已就绪。
|
||
是否让 AI Agent 使用索引查询能力?
|
||
```
|
||
|
||
3. 用户点击“启用”;
|
||
4. 扩展默认注册 MCP Server;
|
||
5. 同时询问/默认勾选:
|
||
|
||
```text
|
||
[✓] 同时安装 RA3 Mod XML Agent Skill(推荐)
|
||
```
|
||
|
||
6. 写入 MCP 配置;
|
||
7. 安装 Skill 到 `~/.agents/skills/ra3-mod-xml/`;
|
||
8. 提供“安装到其他位置”或“手动操作指南”。
|
||
|
||
### 4.8 安全设计
|
||
|
||
- 本地查询服务只监听 `127.0.0.1`;
|
||
- 使用随机 token;
|
||
- MCP Server 默认只读,不提供文件修改/命令执行工具;
|
||
- 写 MCP 配置前必须用户同意;
|
||
- Skill 安装目录记录 managed marker,避免覆盖用户自定义内容。
|
||
|
||
---
|
||
|
||
## 5. 实施阶段
|
||
|
||
### Phase 1:外部快照格式与导出命令
|
||
|
||
- 新增 `src/agent/snapshot.ts`
|
||
- 新增命令:`ra3modxml.exportIndexSnapshot`
|
||
- 定义 `schemaVersion`
|
||
- 从 `ModIndex` 生成可序列化快照
|
||
- 原子写入 `~/.ra3modxml/snapshots/current.json.gz`
|
||
- 输出状态元数据
|
||
|
||
### Phase 2:查询核心与 CLI
|
||
|
||
- 复用纯 TS `indexer` 模块;
|
||
- 新增 `src/agent/query.ts`:
|
||
- `findAsset`
|
||
- `findReferences`
|
||
- `isFileActive`
|
||
- `listAssetsByType`
|
||
- `getStatus`
|
||
- 新增 CLI 入口(可选):
|
||
- 不注册 PATH;
|
||
- 通过绝对路径调用;
|
||
- 供脚本/调试使用。
|
||
|
||
### Phase 3:MCP Server
|
||
|
||
- 新增 `src/agent/mcpServer.ts`
|
||
- 支持 stdio 协议;
|
||
- 启动时读取连接信息/快照;
|
||
- 提供前述 MCP 工具;
|
||
- 每个响应带索引状态。
|
||
|
||
### Phase 4:MCP 配置助手
|
||
|
||
- 新增 `src/agent/setup.ts`
|
||
- 创建稳定 launcher;
|
||
- 检测/写入常见客户端配置:
|
||
- Claude Desktop
|
||
- Cursor
|
||
- Codex
|
||
- 通用 JSON
|
||
- 提供“复制 MCP 配置”;
|
||
- 提供手动操作指南。
|
||
|
||
### Phase 5:Agent Skill 安装器
|
||
|
||
- 新增 `src/agent/skill.ts`
|
||
- 生成 `SKILL.md` + `references/query-guide.md`
|
||
- 默认安装到 `~/.agents/skills/ra3-mod-xml/`
|
||
- 可选安装到:
|
||
- `~/.claude/skills/ra3-mod-xml/`
|
||
- 项目 `.agents/skills/`
|
||
- 项目 `.claude/skills/`
|
||
- 自定义位置
|
||
- 记录已安装位置,扩展升级时自动同步;
|
||
- 不覆盖用户自定义内容。
|
||
|
||
### Phase 6:Live 查询与快照合并
|
||
|
||
- 在扩展内启动本地只读查询服务;
|
||
- MCP Server 在线时优先转发到扩展;
|
||
- 离线时读取 `current.json.gz`;
|
||
- 实现快照 quiet-period 合并写入,避免频繁全量落盘;
|
||
- 实现状态机:`no_index / building / ready_xml / ready / stale / error`。
|
||
|
||
### Phase 7:测试、文档、发布
|
||
|
||
- 测试快照导出/导入一致性;
|
||
- 测试 MCP 工具查询;
|
||
- 测试 Skill 安装/更新/卸载;
|
||
- 测试持续编辑场景下的状态与 stale 行为;
|
||
- 更新 README / docs;
|
||
- 发布新版本。
|
||
|
||
---
|
||
|
||
## 6. 预估文件改动
|
||
|
||
### 新增文件
|
||
|
||
```text
|
||
src/agent/
|
||
types.ts
|
||
snapshot.ts
|
||
query.ts
|
||
mcpServer.ts
|
||
setup.ts
|
||
skill.ts
|
||
localServer.ts
|
||
launcher.ts
|
||
|
||
docs/
|
||
ai-agent-integration-plan.md
|
||
```
|
||
|
||
### 修改文件
|
||
|
||
```text
|
||
package.json # commands / configuration
|
||
src/extension.ts # 注册命令、设置、初始化
|
||
src/workspace.ts # 暴露 index status / snapshot 发布钩子
|
||
src/features/agentSetup.ts # 用户引导 UI(或并入 agent/setup.ts)
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 验收标准
|
||
|
||
1. 用户安装/更新扩展并完成一次索引后,可以看到“启用 AI Agent 访问”的引导。
|
||
2. 用户点击启用后,不需要手动配置 PATH,不需要手动查找 workspaceStorage。
|
||
3. 常见 AI 客户端能通过 MCP 调用索引工具。
|
||
4. Agent 能正确回答:
|
||
- “某 asset 定义在哪个文件?”
|
||
- “谁引用了这个 asset?”
|
||
- “这个 XML 文件是否真的被 Include?”
|
||
5. 查询接口在无索引/索引更新中不会返回误导性空结果。
|
||
6. 用户连续编辑保存时,不会因为频繁快照写入而产生明显卡顿或磁盘膨胀。
|
||
7. Skill 安装后,Agent 知道优先使用 MCP 查询,而不是全文 grep。
|
||
8. 扩展升级后,MCP 配置仍然有效,Skill 能自动同步到已安装位置。
|
||
|
||
---
|
||
---
|
||
|
||
# 第二部分:二期计划(讨论结论 + 实施)
|
||
|
||
> 讨论时间:2026-09-10
|
||
> 主题:Skill 的项目识别边界、正向/间接引用查询的取舍、MCP 与 localhost 架构现状
|
||
|
||
---
|
||
|
||
## 8. 讨论结论一:谁来判定"这是不是一个红警 3 模组项目"
|
||
|
||
### 问题
|
||
|
||
Skill 需要说明"何时应该被使用"和"适用范围"。这要求 Agent 能理解什么是
|
||
"红警 3 模组项目里的 XML",同时避免把无关项目误判为 RA3 模组。
|
||
|
||
问题在于:这个判断应该由扩展/Skill 自己解释(甚至给出推断规则),还是应该
|
||
由项目开发者在自己的项目文档 / `AGENTS.md` 里声明?
|
||
|
||
### 结论:分层,且 Skill 不做复杂推断
|
||
|
||
| 层 | 职责 | 理由 |
|
||
|---|---|---|
|
||
| **Skill** | 说明能力覆盖范围 + 少量正/负信号 + 便宜的探测方式 | Skill 是用户全局的,会在**所有**对话里加载,必须简短且能自我限制 |
|
||
| **项目开发者**(`AGENTS.md` / README / 项目标记) | 声明"这个仓库是什么项目、要不要用这些工具" | 只有项目自己知道自己是什么,这是每个仓库的局部事实 |
|
||
| **MCP 工具自身** | 用响应证明自己是否可用 | `get_status` 是最便宜的判定,不应靠读文档猜 |
|
||
|
||
### 为什么不把判断规则写进 Skill
|
||
|
||
- Skill 在 `~/.agents/skills/` 下全局生效;如果写一堆判定规则,会在与 RA3
|
||
无关的项目里制造"要不要用这个工具"的注意力噪音。
|
||
- 规则永远不完备。真实反例:
|
||
- 目录里有 `Schemas/xsd/CnC3Types.xsd` → 可能只是 SDK 本身,不是 mod;
|
||
- 只有一堆从 SDK 抄来的 `.xsd` → 是"RA3 相关",但没有 mod 数据;
|
||
- `CnC3Types.xsd` 里的 `CnC3` 指 **C&C3**(命令与征服 3 / 凯恩之怒),
|
||
所以"存在 CnC3Types.xsd" **不等于** "是 RA3 模组";
|
||
- 有 `Data/Mod.xml` / `*.babproj` / `Data/additionalmaps/mapmetadata_*.xml`
|
||
→ 基本可确认是 **SAGE / BinaryAssetBuilder** 系项目(RA3 只是其中之一)。
|
||
- 一旦误判,Agent 会拿 RA3 的索引数据去回答另一个项目的问题,比不使用更糟。
|
||
|
||
### 落地方案
|
||
|
||
**Skill 侧**(`src/agent/skill.ts` 的 `SKILL_MD`)新增 applicability 段落:
|
||
|
||
- 正向信号(任一命中即可尝试):
|
||
`Data/Mod.xml`;`Data/additionalmaps/mapmetadata_*.xml`;`*.babproj`;
|
||
XML 根为 `<AssetDeclaration>`;使用 `<Includes><Include source="DATA:…"/></Includes>`。
|
||
- 明确否定:无关仓库、仅含拷贝 `.xsd` 的项目、通用 XML 配置、构建脚本、
|
||
非 SAGE 游戏项目。
|
||
- 便宜的探针:不确定时先调 `get_status`;它返回索引归属的 `projectDir`。
|
||
如果与当前工作区根不一致,或状态是 `no_index`,就**停止使用索引工具**,
|
||
改为直接读文件。
|
||
|
||
这样即使误触发,成本也只是一次 `get_status`,不会污染后续推理。
|
||
|
||
**项目开发者侧**:在项目根 `AGENTS.md` 写一句即可,例如:
|
||
|
||
```markdown
|
||
## RA3 Mod tooling
|
||
This repository is a Red Alert 3 mod (SAGE XML). When locating asset
|
||
definitions or references, prefer the `ra3-mod-xml` MCP tools
|
||
(`find_asset`, `find_references`, `is_file_active`) over full-text search.
|
||
```
|
||
|
||
扩展**不自动**往用户仓库写标记文件(如 `.agents/ra3-mod-xml-project.json`)
|
||
作为二期内容;若将来提供,必须显式确认。
|
||
|
||
---
|
||
|
||
## 9. 讨论结论二:要不要提供"某个 Asset 直接/间接引用的所有 Asset"
|
||
|
||
### 场景
|
||
|
||
用户要求 Agent"查阅或修改雅典娜炮的武器"。Agent 需要:
|
||
|
||
1. 找到雅典娜炮的 GameObject;
|
||
2. 读源码,发现 `<FireWeaponUpdate />` / `<WeaponSetUpdate />` 等子元素,
|
||
在这些子元素里找到武器引用;
|
||
3. 若 `<CreateObjectDie />` 引用了临时物体,还要去读临时物体的 GameObject,
|
||
在它的 `FireWeaponUpdate` 里找武器;
|
||
4. 若雅典娜炮只有 `FireWeaponUpdate`、`WeaponSetUpdate` 在
|
||
`inheritFrom` 的 `BaseCannon` 上,还要读 `BaseCannon` 并综合判断。
|
||
|
||
评估问题:
|
||
|
||
- 是否有必要提供"某个 Asset 直接或间接引用的所有 Asset"?
|
||
- 只给精炼 asset 列表,Agent 不知道是哪个子元素导致的引用,有用吗?
|
||
- 若同时提供上下文,磁盘/内存/性能允许吗?内容长度会不会反而比直接读源码更贵?
|
||
- 相对直接读源码未必有显著提升,反而增加注意力噪音?
|
||
- 间接引用会不会多项式/指数爆炸?
|
||
|
||
### 先指出当前实现的真实缺口
|
||
|
||
现在的索引只有**反向**引用(谁引用了我),没有**正向**查询(我引用了谁)。
|
||
`find_references(AthenaCannon)` 返回的是"谁引用了雅典娜炮",而不是"雅典娜炮
|
||
用了哪些武器"。所以上述 Agent 工作流**目前完全无法用工具完成**,只能读源码。
|
||
|
||
因此问题不是"要不要锦上添花",而是这里确实缺一个基础能力。
|
||
|
||
### 结论:不提供"所有可达 Asset 的扁平闭包",改提供"有界 + 带 provenance 的边列表"
|
||
|
||
**不采用扁平静态闭包的理由:**
|
||
|
||
1. **丢失 provenance,Agent 仍然要读源码。** 到底是 `FireWeaponUpdate` 的武器、
|
||
`WeaponSetUpdate` 的武器、`CreateObjectDie` 临时物体的武器,还是
|
||
`inheritFrom` 继承来的武器?这决定了 Agent 改哪个文件、加不加
|
||
`xai:joinAction`。丢掉上下文,列表基本没用。
|
||
2. **尺寸会失控。** 一个 GameObject 的可达闭包可能是
|
||
`Weapon → Projectile → Warhead → DamageNugget → FX → Particle → Texture`、
|
||
`Model → Mesh → Material → Texture`、`AttributeModifier`、`Upgrade`、`Command`、
|
||
`Button`……Corona 规模下单个单位几百到上千节点完全可能。200 条边就是几万
|
||
token,**比直接读 3 个小 XML 文件贵得多**。
|
||
3. **注意力噪音。** 列表里 80% 是与当前问题无关的 Texture / FX 时,推理质量下降。
|
||
4. **"间接"的诱惑会持续膨胀。** 一旦提供无界闭包,Agent 会习惯性调用它,
|
||
然后被淹没。
|
||
|
||
**但以下特性确实值得工具化:**
|
||
|
||
- 多跳遍历是**纯机械**的,正是工具该做的事;
|
||
- `inheritFrom` 链 + `xai:joinAction` 语义很微妙,Agent 手推容易错;
|
||
- 每跳都 read 一个文件,Corona 下成本和 token 都很高;
|
||
- 工具可以做到**确定性、不遗漏**(按 XSD 判定"哪些属性是引用",
|
||
而不是靠 Agent 逐个注意)。
|
||
|
||
**设计要点(二期实现遵循):**
|
||
|
||
- 返回**边列表**而不是节点集合。每条边携带:
|
||
- `from`(effective asset)、`to`(解析出的定义位置)、
|
||
- `via.kind`(`attribute` / `content` / `inheritFrom`)、
|
||
- `via.element`、`via.parent`、`via.attribute`,
|
||
- `source.file` / `source.line`(XML 实际所在位置),
|
||
- `definedIn`(当该边来自 `inheritFrom` 祖先的 XML 时,标明真正写它的资产)。
|
||
- **默认 `depth: 1`,硬上限 3。** Agent 拿到直接引用后自己决定是否对某个具体
|
||
节点递归,比一次性 dump 闭包更省 token、更有针对性。
|
||
- **`targetTypes` 过滤**是抑制噪音最有效的手段。雅典娜炮场景就是
|
||
`targetTypes: ["WeaponTemplate"]`(可选再带 `GameObject` 给 `CreateObjectDie`),
|
||
返回可能只有 3–10 条边。
|
||
- **`inheritFrom` 不消耗 depth**:继承是资产自身定义的一部分,不是运行时引用。
|
||
因此在同一 depth 内递归遍历继承链(链长上限 8),并把 `definedIn` 标出来。
|
||
- **截断时返回 `omittedByTargetType` 摘要**,让 Agent 知道被砍掉了什么形状的数据,
|
||
可以精确下钻,而不是无脑重试或放弃。
|
||
- **不提供"合并后的有效值"**:`joinAction` 的 Replace/Remove/merge 语义算错的风险
|
||
太高,一期不做。
|
||
|
||
**关于爆炸和成本:**
|
||
|
||
- 算法上不是指数:带 visited-set 的 BFS 是 `O(V+E)`。真正的风险是**语义宽度**,
|
||
不是复杂度,因此用 `depth` + `targetTypes` + `maxEdges` 三重限制。
|
||
- **只做 live 查询**(VS Code 打开时可用)。要拿元素上下文,最干净的做法是按需
|
||
解析定义所在文件的 DOM,而不是往 records 缓存里塞元素名/属性名 —— Corona 有
|
||
~98k 条引用记录,再塞 5 个字符串字段会让常驻内存明显膨胀,为一个默认 `depth=1`
|
||
的功能付出这个成本不划算。
|
||
- 离线快照路径明确返回"需要 live index",而不是静默返回空结果。
|
||
|
||
---
|
||
|
||
## 10. 讨论结论三:MCP / localhost 的实际架构与多窗口缺陷
|
||
|
||
### 当前架构(事实)
|
||
|
||
```text
|
||
AI 客户端
|
||
│ stdio (JSON-RPC, MCP)
|
||
▼
|
||
dist/agent/mcpServer.js ← 由 AI 客户端启动,参数 --project <dir>
|
||
│ HTTP (127.0.0.1:临时端口, Bearer token)
|
||
▼
|
||
VS Code 扩展进程内的 localServer ← 仅在"启用 AI Agent 访问"后启动
|
||
│
|
||
├─ 在线 → ws.activeIndex() 实时内存索引
|
||
└─ 离线 → ~/.ra3modxml/snapshots/project-<hash>.json.gz
|
||
```
|
||
|
||
- **AI 客户端不需要知道端口。** MCP Server 自己读
|
||
`~/.ra3modxml/endpoint.json` 拿 `url` + `token` 再 `fetch`。localhost 层是
|
||
纯实现细节。
|
||
- **端口**:`server.listen(port ?? 0, "127.0.0.1")`,即由操作系统分配临时端口,
|
||
不会冲突;当前未传 `port`,所以永远是临时端口。
|
||
- **鉴权**:`Authorization: Bearer <token>`,token 为启动时随机生成;绑定
|
||
`127.0.0.1`,局域网访问不到。
|
||
- **降级**:live 查询失败(endpoint 不存在 / 连接被拒 / 超时 1.5s)时回退到快照。
|
||
|
||
### 多项目 / 多 VSCode 窗口的真实缺陷
|
||
|
||
`endpoint.json` 是**全局唯一**的一个文件,所有 VSCode 窗口都往它写入:
|
||
|
||
1. **最后启用的窗口覆盖前面所有窗口。** 两个窗口都启用了 Agent 访问时,
|
||
只有后启动的那个能被 MCP Server 找到。
|
||
2. **可能串项目(严重)。** 假设窗口 B 覆盖了 endpoint,而某个 MCP Server 是用
|
||
`--project 窗口A` 启动的,它的 `tryLiveQuery` 会打到**窗口 B** 的 server,
|
||
拿到窗口 B 的索引数据,而返回值里没有任何东西能暴露这个不一致。
|
||
这比 `no_index` 危险得多 —— 会静默给出错误答案。
|
||
3. **关闭任意窗口都会 `clearEndpoint()`**,把还开着的其他窗口的 live 通道一起断掉。
|
||
4. **同一窗口内多项目也不稳。** live server 用
|
||
`getIndex: () => ws.activeIndex()`,而 `activeIndex()` 跟着**当前活动编辑器**走。
|
||
用户切到另一个项目的文件,live 查询就会静默换项目;而 `endpoint.json` 里的
|
||
`projectDir` 只在启动时写一次,之后不更新。
|
||
5. `processId` 虽然写进了 endpoint,但**从未被校验**;VSCode 崩溃后残留的 endpoint
|
||
只能靠连接失败兜底。
|
||
6. 离线快照路径是**正确**的 —— `snapshotPathForProject()` 按项目路径做 hash,
|
||
多项目互不干扰。问题只在 live 层。
|
||
|
||
### 修复方案(二期 A,按性价比排序)
|
||
|
||
1. **live 响应必须带 `projectDir`,MCP 侧必须校验它和 `--project` 一致**,
|
||
不一致就直接走快照。这是最小的正确性补丁,最先做。
|
||
2. **endpoint 按项目存**:`~/.ra3modxml/endpoints/<project-hash>.json`,
|
||
MCP Server 按 `--project` 找对应的那个;同时保留 `endpoint.json` 作为
|
||
"最近"的兼容入口。
|
||
3. **live server 支持 `?project=` 路由**,而不是只认 `activeIndex()`。
|
||
扩展侧加 `indexForProject(projectDir)`,一个窗口的一个 server 就能服务它
|
||
所有已索引项目,也就不怕用户切编辑器。
|
||
4. **关闭时只清自己的那一条**,不无条件删全局 endpoint。
|
||
5. **校验 `processId` 是否存活**,不存活就当 endpoint 失效。
|
||
6. `tryLiveQuery` 失败后做几秒的"live 不可用"负缓存,避免每次调用都试一次。
|
||
7. `snapshotBaseName` 换成 sha1 前缀 + 可读 slug(与 `diskCacheKey` 一致的做法),
|
||
降低碰撞概率。
|
||
|
||
---
|
||
|
||
## 11. 二期实施清单
|
||
|
||
### 二期 A:多窗口 / 多项目正确性
|
||
|
||
- `src/agent/snapshot.ts`:新增 `projectHash()`;`snapshotBaseName()` 改为
|
||
`<slug>-<sha1-12>`。
|
||
- `src/agent/endpoint.ts`:新增
|
||
`endpointPathForProject()` / `writeEndpointForProject()` /
|
||
`readEndpointForProject()` / `clearEndpointForProject()` / `isProcessAlive()`。
|
||
- `src/workspace.ts`:新增 `indexForProject(projectDir)`。
|
||
- `src/agent/liveQuery.ts`:`liveStatus()` 在没有索引时回落到请求的 `projectDir`。
|
||
- `src/agent/localServer.ts`:`getIndex(projectDir?)` 路由、异步 handler、
|
||
`/projects` 端点、所有响应携带 `index.projectDir`。
|
||
- `src/agent/mcpServer.ts`:按项目读 endpoint、校验 `projectDir` 与 `processId`、
|
||
`?project=` 透传、live 失败负缓存、响应 `projectDir` 二次校验。
|
||
- `src/extension.ts`:启动时按项目写 endpoint、关闭时只清自己写过的那些。
|
||
|
||
### 二期 B:Skill 适用范围
|
||
|
||
- `src/agent/skill.ts`:`SKILL_MD` 增加 applicability 段落(正向信号、明确否定、
|
||
`get_status` 探针);保持"不引用工作区文档"的原则。
|
||
|
||
### 二期 C:`get_asset_references`
|
||
|
||
- `src/agent/forwardRefs.ts`(新增):DOM-based、有界、带 provenance 的正向引用遍历。
|
||
- `src/agent/localServer.ts`:新增 `GET /get_asset_references`。
|
||
- `src/agent/mcpServer.ts`:新增 `get_asset_references` 工具(live-only,
|
||
离线时返回明确的"需要 live index"说明)。
|
||
- `src/extension.ts`:为 live server 提供 `loadFile()`(读文本 + 解析 + LineMap)。
|
||
|
||
### 二期 D:测试
|
||
|
||
- `test/agentEndpoint.test.mjs`:按项目 endpoint、进程存活、清理隔离。
|
||
- `test/agentLocalServer.test.mjs`:`?project=` 路由、`projectDir` 校验、
|
||
`/get_asset_references`。
|
||
- `test/agentForwardRefs.test.mjs`:直接引用、继承链、`CreateObjectDie` 内容引用、
|
||
`targetTypes` 过滤、`maxEdges` 截断、`depth` 上限。
|
||
- `test/agentSkill.test.mjs`:Skill 文本包含 applicability 段落且不含工作区文档引用。
|
||
- `test/agentMcpRouting.test.mjs`:live URL 固定 `project`、跨项目响应拒绝。
|
||
|
||
---
|
||
|
||
## 12. 二期实施结果
|
||
|
||
全部完成,`npx tsc --noEmit` 通过,`test/*.test.mjs` 共 **264/264** 通过。
|
||
|
||
### 新增 / 修改文件
|
||
|
||
```text
|
||
新增:
|
||
src/agent/forwardRefs.ts 有界、带 provenance 的正向引用遍历
|
||
test/agentForwardRefs.test.mjs
|
||
test/agentMcpRouting.test.mjs
|
||
|
||
修改:
|
||
src/agent/endpoint.ts 按项目 endpoint + isProcessAlive()
|
||
src/agent/snapshot.ts projectHash() / projectSlug() / snapshotBaseName()
|
||
src/agent/liveQuery.ts liveStatus() 回落请求的 projectDir
|
||
src/agent/localServer.ts 异步 handler、?project= 路由、/projects、/get_asset_references
|
||
src/agent/mcpServer.ts 按项目 endpoint、projectDir 校验、负缓存、get_asset_references
|
||
src/agent/skill.ts applicability 段落 + get_asset_references 用法
|
||
src/workspace.ts 新增 indexForProject()
|
||
src/extension.ts 按项目写/清 endpoint、loadFile、endpoint 刷新
|
||
test/agentEndpoint.test.mjs
|
||
test/agentLocalServer.test.mjs
|
||
test/agentSkill.test.mjs
|
||
```
|
||
|
||
### 关键行为
|
||
|
||
**多窗口 / 多项目**
|
||
|
||
- `~/.ra3modxml/endpoints/<slug>-<sha1-12>.json` 一项目一文件;MCP Server 先读自己项目的
|
||
文件,读不到才退回全局 `endpoint.json`,且仅在 `projectDir` 匹配时使用。
|
||
- 每个 live 请求带 `?project=`;server 用 `ModWorkspace.indexForProject()` 路由,
|
||
不再依赖"当前活动编辑器"。
|
||
- MCP 侧对响应里的 `index.projectDir` 做二次校验,不匹配就拒答并提示重新启用,
|
||
而不是给一个看起来合理但属于别的项目的答案。
|
||
- 关闭窗口只删除本窗口写过的 endpoint 文件。
|
||
- `processId` 不存活时 endpoint 视为失效。
|
||
- live 连接失败后有 5 秒负缓存,避免每次工具调用都探一次。
|
||
|
||
**Skill**
|
||
|
||
- 新增 `When this skill applies` 段落:正向信号(`Data/Mod.xml`、
|
||
`mapmetadata_*.xml`、`*.babproj`、`<AssetDeclaration>`、
|
||
`uri:ea.com:eala:asset`)、明确否定规则(无关仓库、仅含拷贝 `.xsd` 的项目)、
|
||
以及 `CnC3Types.xsd` 属于 C&C3 而非 RA3 的反例。
|
||
- 规定用 `get_status` 做便宜探针:状态为 `no_index` 或 `projectDir` 不匹配时
|
||
停止使用索引工具,改为直接读文件。
|
||
- 仍然不引用任何工作区文档(有测试断言)。
|
||
|
||
**`get_asset_references`(正向引用)**
|
||
|
||
- 返回**边**:`from` / `to` / `via{kind,element,parent,attribute}` /
|
||
`source{file,line,character}` / `definedIn` / `value`。
|
||
- `depth` 默认 1、上限 3;`inheritFrom` 不消耗 depth,祖先 XML 在同层遍历,
|
||
用 `definedIn` 标出真正写这条边的资产(解决"雅典娜炮自己没有 WeaponSetUpdate,
|
||
但 BaseCannon 有"的场景)。
|
||
- `targetTypes` 按 XSD 可赋值性过滤,但 `inheritFrom` 边始终保留,因为
|
||
它解释了其余边写在哪里。
|
||
- `maxEdges` 截断时返回 `truncated` + `omittedByTargetType`(只统计因上限被丢弃的边,
|
||
已被 `targetTypes` 过滤的不计入)。
|
||
- 只走 live 路径;不可用时返回明确的错误说明,而不是空结果。
|
||
|
||
### 已知限制
|
||
|
||
- `get_asset_references` 不计算 `xai:joinAction`(`Replace`/`Remove`)合并后的
|
||
有效值,需要调用方自行确认。
|
||
- 只有 live 路径支持正向引用;离线快照路径明确返回"需要 live index"。
|
||
- `snapshotBaseName` 改为 `<slug>-<sha1-12>`,旧的 `project-<hash>.json.gz`
|
||
快照文件不会被自动清理(该功能尚未发布,无兼容负担)。
|
||
- 本沙箱禁止 spawn 子进程,因此 `node --test` 与 `npm run build` 无法直接运行;
|
||
验证方式为:逐个 `node test/*.test.mjs`、直接调用 esbuild CLI 构建 dist、
|
||
用管道驱动 `dist/agent/mcpServer.js` 做端到端 smoke test。
|
||
|
||
---
|
||
---
|
||
|
||
# 第三部分:三期计划(讨论结论 + 实施)
|
||
|
||
> 讨论时间:2026-09-10
|
||
> 主题:Agent 自发现 MCP 的现实边界、崩溃残留清理、CLI 定位、无 Node 环境
|
||
|
||
---
|
||
|
||
## 13. 讨论结论:自操作 Agent 能否自行发现并配置 MCP
|
||
|
||
### 现状(核实过代码)
|
||
|
||
`SKILL_MD` 全文不含 `~/.ra3modxml`、launcher 路径、CLI 或任何"工具不存在时怎么办"
|
||
的说明。它的第一条指令是"调用 `get_status`"。因此当 skill 已加载但 MCP 工具
|
||
不在工具列表里时,agent 只能得到 unknown tool,然后没有下一步。
|
||
|
||
三个独立缺口:
|
||
|
||
1. **没有发现面。** 磁盘上的文件名带 `sha1-12` 哈希与 slug
|
||
(`endpoints/<slug>-<sha1-12>.json`、`snapshots/<slug>-<sha1-12>.json.gz`),
|
||
只有读过本仓库源码的人才知道这套命名。agent 没有"读一个文件就知道全貌"的入口。
|
||
2. **skill 与 MCP 是独立安装的。** `installAgentSkill` 装的是
|
||
`~/.agents/skills/ra3-mod-xml/`,与 MCP 配置无关,所以"skill 在、MCP 不在"
|
||
是常见状态,而这恰恰是最需要 fallback 的场景。
|
||
3. **多数 harness 不允许 agent 给自己挂 MCP。** Claude Code 需要重启才能加载新的
|
||
MCP server(相关 issue 与 SIGHUP workaround 见调研),Reddit 上也有"每次装
|
||
MCP 都要手动重启"的反馈;反例是 LibreChat 的设置面板明确"take effect without
|
||
a restart"。所以这是 **harness 相关**:agent 可以**写**配置,但通常不能让自己
|
||
**当前会话**用上,只能为下一次会话准备好。
|
||
|
||
### 结论
|
||
|
||
- **skill 不应假设 MCP 已就绪**,而要给出"如何触达索引"的阶梯:
|
||
1. MCP 工具已在工具列表 → 直接用;
|
||
2. 否则读发现面清单;不存在 → 告诉用户去 VS Code 跑一次 Enable,**不要瞎猜**;
|
||
存在 → 用 shell 走 stdio,或用 CLI;
|
||
3. 若能写自己的 MCP 配置,可以提议代写,但必须告知**下个会话才生效**。
|
||
- **"需要重启才生效"这类细节不必写进 skill。** 新一代自操作 agent 本来就该自己
|
||
探测能力边界;skill 只需提示它"检查自己的能力范围并告知用户"。
|
||
- **不要把各 harness 的配置路径写进 skill 文本。** 那些路径会变,写进 skill 等于
|
||
把易变知识固化进 agent 上下文。逻辑留在 `setup.ts`
|
||
(已有 `addMcpServerToConfigFile` / `commonMcpConfigTargets`),通过 CLI 暴露成
|
||
`configure --harness <name>`;skill 只说"在用户同意后可以运行这条命令"。
|
||
|
||
---
|
||
|
||
## 14. 讨论结论:崩溃残留与 `instances/` 注册表
|
||
|
||
### 问题确认
|
||
|
||
核实结果:全仓只有两处 `rm`,都在 `stopAgentLocalServer`(即优雅 dispose)中。
|
||
`endpoints/` **没有任何剪枝逻辑**。因此:
|
||
|
||
- **崩溃后文件永久残留**,只有下次打开**同一个工作区**才会覆盖同名文件;
|
||
删掉项目、移动目录、改 SDK 路径后,旧文件永远躺着。
|
||
- **PID 复用是真实风险。** Windows PID 会被回收;若残留 PID 恰好被无关进程占用,
|
||
`isProcessAlive` 返回 true → 连接失败 → 5 秒负缓存 → 反复重试。不会给出错误
|
||
答案(`responseProjectMismatch` 仍在),但会持续做无用功。
|
||
|
||
### 结论:不要"共享一个 JSON + 加锁合并"
|
||
|
||
共享单文件需要锁文件(Windows 上 `wx` + 退避 + 陈旧锁窃取)、需要
|
||
read-modify-write 合并(否则并发写互相丢条目),而锁本身也会被崩溃留下
|
||
——**为了修 A 的崩溃问题引入了 B 的崩溃问题**。
|
||
|
||
改用**写入方互不重叠**的结构:
|
||
|
||
```text
|
||
~/.ra3modxml/
|
||
instances/
|
||
vscode-<pid>-<rand>.json ← 每个 VS Code 窗口一个,只有属主会写
|
||
index.json ← 只读合并视图,供 agent/人发现
|
||
snapshots/<slug>-<sha1-12>.json.gz
|
||
skill-install.json
|
||
```
|
||
|
||
- **无锁、无合并**:每实例只写自己那一个文件,天然无竞争。
|
||
- **崩溃自愈**:任何实例激活时扫一遍 `instances/`,`isProcessAlive(pid) === false`
|
||
的直接 unlink,不需要等"下次打开同一个工作区"。
|
||
- **读方仍要校验**:liveness = PID 存活 **且** 能连上;PID 复用场景下连接失败即
|
||
视为死,并顺带触发剪枝。
|
||
- `index.json` 是纯只读派生物(由 `instances/` 生成),机器协调文件与人类/agent
|
||
发现面分离。
|
||
|
||
`endpoints/` 保留为兼容读取路径,不再主动写入。
|
||
|
||
---
|
||
|
||
## 15. 讨论结论:CLI 定位与"稳定 IPC"
|
||
|
||
### 澄清:端口从未写进 harness 永久设置
|
||
|
||
写进配置的是 launcher:
|
||
|
||
```json
|
||
{ "mcpServers": { "ra3-mod-xml": { "command": "~/.ra3modxml/ra3-mod-xml-mcp.cmd", "args": ["--project", "..."] } } }
|
||
```
|
||
|
||
端口由 `instances/*.json` 桥接,每次开新会话时由 MCP server 现读。所以
|
||
"临时端口会变"对 harness 完全透明。换命名管道/UDS 的收益不是"稳定性",而是:
|
||
|
||
- Windows 防火墙对监听 TCP socket 可能弹窗,命名管道不会;
|
||
- 不占端口;
|
||
- 权限模型更自然(管道可设 ACL;TCP loopback 任何本机进程都能连,现在靠 token)。
|
||
|
||
代价:Node 的 `fetch` 不支持 socketPath,客户端要从 `fetch` 换成
|
||
`http.request({ socketPath })`(服务端 `listen(pipePath)` 即可,路由不用改)。
|
||
|
||
### CLI 定位:薄
|
||
|
||
CLI 只做两件事:**读磁盘快照** + **转发给扩展(live)**。不要在 CLI 里重新实现
|
||
"高级功能",那等于写两遍。
|
||
|
||
**但要注意**:转发逻辑现在锁死在 `mcpServer.ts` 里
|
||
(`tryLiveQuery` / `liveUrlForTool` / `responseProjectMismatch` / 负缓存)。
|
||
若 CLI 再写一遍就是重复。因此必须先抽 `src/agent/liveClient.ts`,让
|
||
`mcpServer.ts` 与 `cli.ts` **同时**依赖它:
|
||
|
||
- 换传输层时只改一个文件;
|
||
- `projectDir` 校验、负缓存等安全逻辑不会在两个入口之间漂移。
|
||
|
||
---
|
||
|
||
## 16. 讨论结论:无 Node 环境(已验证)
|
||
|
||
### 问题
|
||
|
||
- 不能假定用户装了 Node。
|
||
- `writeLauncher` 生成的是裸 `node "<server>" ...`,所以**没装 Node 的用户
|
||
点完 Enable 之后 MCP 起不来**,而且不会报"缺 node"。这是**已存在的缺口**,
|
||
不是未来风险。
|
||
- 打包自包含或原生二进制是否必要?
|
||
|
||
### 结论:不需要打包,VS Code 自带运行时,只是还没用它
|
||
|
||
VS Code 的 Electron 二进制可以当 Node 用:
|
||
|
||
```bat
|
||
@echo off
|
||
set ELECTRON_RUN_AS_NODE=1
|
||
"C:\...\Microsoft VS Code\Code.exe" "<ext>\dist\agent\mcpServer.js" --project "D:\..."
|
||
```
|
||
|
||
**本机实测(2026-09-10,VS Code 1.135.0 / Electron 42.8.1)**:
|
||
|
||
| 项目 | 结果 |
|
||
|---|---|
|
||
| `ELECTRON_RUN_AS_NODE=1` 生效 | ✅ `process.versions.electron = 42.8.1` |
|
||
| Node 版本 | `24.18.1`(系统 Node 为 24.16.0) |
|
||
| 必需模块(`fs`/`fs/promises`/`path`/`os`/`http`/`readline`/`zlib`/`crypto`/`util`/`net`/`child_process`) | ✅ 全部可载入 |
|
||
| `node:test` | ✅ 可载入 |
|
||
| `forwardRefs.parseLoadedXml` | ✅ `elements=2` |
|
||
| `localServer.startLocalServer` + HTTP 请求 | ✅ 正常返回 |
|
||
| `zlib` gzip/gunzip 往返 | ✅ |
|
||
| **打包后的 `dist/agent/mcpServer.js` 完整 stdio 会话** | ✅ `initialize` / `tools/list`(9 个工具) / `tools/call get_status` 全部正确 |
|
||
|
||
因此:
|
||
|
||
- **零外部依赖**,只装 VS Code 就够;
|
||
- 路径来自 `process.execPath`,自动适配 Code / Insiders / VSCodium / 各平台;
|
||
- VS Code 升级后路径变了也没关系——扩展每次激活重写 launcher,而 launcher 本身
|
||
在 `~/.ra3modxml/` 下路径稳定;
|
||
- 需要 fallback:Electron 二进制被移动/卸载时回退到 PATH 上的 `node`。
|
||
|
||
已知代价(实测未测,需留意的边界):
|
||
|
||
- Electron 以 Node 模式启动比真 Node 慢(握手延迟增加,对 MCP 应无碍);
|
||
- 远程/WSL/容器场景下 `process.execPath` 是 server 端二进制,MCP 必须跑在远端
|
||
—— 独立边界问题,本期不解决。
|
||
|
||
### 如何在"本机有 Node"的前提下测试"无 Node"
|
||
|
||
不需要真的卸载 Node。要做的是**证明 Electron 路径可以独立工作**,即整条链路
|
||
不依赖"PATH 上能解析到 `node`":
|
||
|
||
1. 直接只用 Electron 二进制驱动 `dist/agent/mcpServer.js`(已做,见上表);
|
||
2. 让解析出的 launcher **只用** Electron,不回落;
|
||
3. 断言 launcher 文本中不出现裸 `node`(当 Electron 可用时)。
|
||
|
||
这三点都在本环境可做,无需移除 Node。
|
||
|
||
---
|
||
|
||
## 17. 三期实施清单
|
||
|
||
| 优先级 | 内容 | 理由 |
|
||
|---|---|---|
|
||
| 1 | 运行时解析(`src/agent/runtime.ts`)+ launcher 优先 Electron | 现在是"配好了但可能起不来",最伤 |
|
||
| 2 | 抽 `src/agent/liveClient.ts` | 后续所有传输层改动的前提;避免转发逻辑写两遍 |
|
||
| 3 | `instances/` + 跨实例剪枝 + 只读 `index.json` | 修崩溃残留,兼做 agent 发现面 |
|
||
| 4 | CLI 瘦身(live 优先 / 快照兜底 / cwd 识别 / `projects`) | 让"没有 MCP 的 agent"也能用 |
|
||
| 5 | 传输层 TCP → 命名管道 / UDS | 收益中等,需 2 完成,可缓 |
|
||
|
||
---
|
||
|
||
## 18. 三期实施结果
|
||
|
||
全部完成。
|
||
|
||
### 新增 / 修改文件
|
||
|
||
```text
|
||
新增:
|
||
src/agent/runtime.ts 运行时解析 + launcher 脚本生成(纯函数,可测)
|
||
src/agent/liveClient.ts 共享 live 转发层(MCP 与 CLI 共用)
|
||
src/agent/instances.ts instances/ 注册表、剪枝、只读 index.json
|
||
test/agentRuntime.test.mjs
|
||
test/agentLiveClient.test.mjs
|
||
test/agentInstances.test.mjs
|
||
test/agentCli.test.mjs
|
||
test/agentLiveE2E.test.mjs
|
||
tools/probe-electron-node.cjs Electron-as-Node 能力探针(保留为证据)
|
||
tools/serve-fake-index.cjs 假 live 索引服务器(手工 smoke 用)
|
||
|
||
修改:
|
||
src/agent/setup.ts writeLauncher 改用 runtime + launcherScript,
|
||
返回 { path, runtime, nodeFree }
|
||
src/agent/mcpServer.ts 改用 LiveClient;新增 list_projects;
|
||
启动时剪枝崩溃实例;require.main 守卫
|
||
src/agent/cli.ts 重写为薄客户端:live 优先 / 快照兜底 /
|
||
cwd 项目识别 / projects / outgoing
|
||
src/extension.ts instances/ 写入与剪枝、index.json 刷新、
|
||
launcher 激活时刷新、nodeFree 警告
|
||
```
|
||
|
||
### 关键行为
|
||
|
||
**无 Node 运行(三期 1)**
|
||
|
||
- launcher 优先用 VS Code 的 Electron 二进制(`ELECTRON_RUN_AS_NODE=1`),
|
||
Node 只作为"Electron 二进制不存在"时的兜底:
|
||
`if not exist "%RA3_RUNTIME%" goto :ra3_node`。
|
||
- 用 `goto` 而不是 `if (...)` 块:批处理里块内的 `%errorlevel%` 在**解析时**展开,
|
||
会导致退出码错误。
|
||
- 扩展激活时会重写 launcher(不只是 Enable 时),所以 VS Code 升级换了路径
|
||
也能自动跟上。
|
||
- 解析不到 Electron 时会警告用户"launcher 将依赖 PATH 上的 Node"。
|
||
|
||
**`instances/` 注册表(三期 3)**
|
||
|
||
```text
|
||
~/.ra3modxml/
|
||
instances/vscode-<pid>-<rand>.json 每窗口一个,只有属主写
|
||
index.json 只读合并视图(不含 token)
|
||
endpoints/<slug>-<sha1-12>.json 兼容读取,不再主动写
|
||
snapshots/…
|
||
skill-install.json
|
||
```
|
||
|
||
- **无锁无合并**:写入方互不重叠,没有 read-modify-write 竞争。
|
||
- **崩溃自愈**:任何实例启动时剪掉 PID 已死的条目,不需要等同一工作区重开。
|
||
- **只剪确定的死 PID**:`isProcessAlive` 对未知/缺失 PID 返回 true,
|
||
所以老格式文件不会被误删。
|
||
- `index.json` 是派生物,只用于发现;断言过其中不含 token。
|
||
|
||
**共享 live 层(三期 2)**
|
||
|
||
`liveClient.ts` 现在同时被 MCP 与 CLI 使用,集中了:
|
||
|
||
- 请求永远带 `?project=`(否则会被活动编辑器的项目回答);
|
||
- `index.projectDir` 不匹配就拒答;
|
||
- 死 PID 视为失效;
|
||
- 失败后 5 秒负缓存(可注入时钟,测试可确定性验证);
|
||
- 端点发现顺序:按项目文件 → 任何 `projects` 含该项目的实例 → 全局兜底指针。
|
||
|
||
**薄 CLI(三期 4)**
|
||
|
||
- `outgoing` / `projects` 是 live-only:不可用时返回**明确原因**,
|
||
而不是空结果(空结果会被 agent 读成"没有引用")。
|
||
- 项目解析顺序:`--project` → `--snapshot` → 从 cwd 向上找 mod 项目根。
|
||
- 退出码:0 成功 / 2 用法错误 / 3 不可用。
|
||
|
||
### 已知限制
|
||
|
||
- `get_asset_references` 不计算 `xai:joinAction`(`Replace`/`Remove`)合并后的
|
||
有效值,需要调用方自行确认。
|
||
- 传输层仍是 loopback HTTP(临时端口 + token)。换命名管道/UDS 的收益是
|
||
免防火墙弹窗与更自然的权限模型,代价是客户端要从 `fetch` 改为
|
||
`http.request({ socketPath })`;因为转发逻辑已集中,改动面只剩
|
||
`liveClient.ts`。
|
||
- 远程 / WSL / 容器场景下 `process.execPath` 是服务端二进制,MCP 必须跑在
|
||
远端;本期未处理。
|
||
- CLI 在"无 Node 且无 VS Code"的环境下无法运行(这是逻辑必然:它需要一个
|
||
运行时)。有 VS Code 时用 Electron 二进制即可。
|
||
|
||
---
|
||
|
||
## 19. 三期测试与验证
|
||
|
||
```
|
||
npx tsc --noEmit 通过
|
||
test/*.test.mjs 37 个文件 / 302 个用例,299 通过,3 跳过
|
||
```
|
||
|
||
跳过的 3 个都是同一原因:本沙箱禁止从 Node 进程 spawn shell(`EPERM`),
|
||
所以"真正拉起 launcher / CLI 子进程"的集成用例会优雅跳过并给出原因。
|
||
它们在本机正常运行时会执行。
|
||
|
||
### Electron-as-Node 实测证据
|
||
|
||
`tools/probe-electron-node.cjs`(VS Code 1.135.0 / Electron 42.8.1,Windows):
|
||
|
||
| 项目 | 结果 |
|
||
|---|---|
|
||
| `ELECTRON_RUN_AS_NODE=1` 生效 | ✅ `process.versions.electron = 42.8.1` |
|
||
| Node 版本 | `24.18.1`(系统 Node 24.16.0) |
|
||
| 必需模块 | ✅ `fs`/`fs/promises`/`path`/`os`/`http`/`readline`/`zlib`/`crypto`/`util`/`net`/`child_process` |
|
||
| `node:test` | ✅ 可载入 |
|
||
| `parseLoadedXml` | ✅ |
|
||
| `startLocalServer` + HTTP 请求 | ✅ |
|
||
| `zlib` gzip/gunzip 往返 | ✅ |
|
||
| **打包后 `dist/agent/mcpServer.js` 完整 stdio 会话** | ✅ initialize / tools/list(10 个) / tools/call |
|
||
|
||
### 无 Node 运行的证明方式
|
||
|
||
不是卸载 Node,而是证明整条链路不依赖"PATH 上能解析到 node":
|
||
|
||
1. 用 `cmd /c` + `ELECTRON_RUN_AS_NODE=1` 直接驱动 `dist/agent/mcpServer.js`
|
||
→ 完整 MCP 会话成功;
|
||
2. 生成的 launcher 里把 **Node 兜底指向一个不存在的路径**,会话仍然成功
|
||
→ 证明走的是 Electron 分支;
|
||
3. 断言 launcher 文本:Windows 下 Node 分支只能通过
|
||
`if not exist "%RA3_RUNTIME%" goto :ra3_node` 到达。
|
||
|
||
### 其他已验证行为
|
||
|
||
- **CLI 会自然退出**:实测一次 live 查询后进程 59ms 内自然退出,
|
||
`fetch` 的 keep-alive 不会吊住 CLI(否则对 agent 是致命的可用性问题)。
|
||
- **live 端到端**:`test/agentLiveE2E.test.mjs` 启动真实 `startLocalServer`,
|
||
按扩展的方式写 instance/endpoint/manifest,然后用**同一个 `LiveClient`**
|
||
跑通全部工具,包括 `get_asset_references` 的 `definedIn` 继承溯源。
|
||
- **跨项目隔离**:为第三个项目创建的客户端在只有 A/B 实例时返回 null,
|
||
不会回落到 A 的数据。
|
||
- **发现降级**:删掉 `endpoints/` 后仍能通过 `instances/` 找到实例。
|
||
- **崩溃剪枝**:插入一个 PID 必死的实例,剪枝后另一个实例仍正常工作;
|
||
清理本窗口实例不影响其他窗口。
|
||
|
||
---
|
||
|
||
## 20. P0 修复(2026-09-10)
|
||
|
||
三期测试暴露 / 评审发现的问题,以下四项已修复:
|
||
|
||
| # | 问题 | 修复 |
|
||
|---|---|---|
|
||
| 1 | `test/agentSkill.test.mjs` 与 `SKILL.md` 措辞不一致(`new session` / `Never fabricate index results` 在源码中丢失),干净重建后测试失败 | 恢复两条护栏;测试同步断言 |
|
||
| 2 | Skill 里的 `CnC3Types.xsd` 说法错误:它是 RA3 Mod SDK 也携带的 SAGE 基础 schema(`sdk.ts` 的 SDK 根标记就是它),原句会让 agent 误判真实 RA3 项目 | 删除该句,改为通用否定规则(SAGE-looking XML / 复制的 `.xsd` / `Data` 目录本身都不构成证据) |
|
||
| 3 | 关闭一个 VS Code 窗口会无条件 `clearEndpoint()` + `writeManifest([])`,抹掉其他仍在服务窗口的 `endpoint.json` / `index.json` | 新增 `refreshDiscovery()`:从幸存 instance 文件重新推导两者;`stopAgentLocalServer` 改用它;新增回归测试 |
|
||
| 4 | `npm run build` 不清理 `dist/`,0.1.26 VSIX 同时带着旧布局的 `dist/mcpServer.js` / `dist/cli.js` | `esbuild.mjs` 构建前 `rm -rf dist` |
|
||
|
||
同时:
|
||
|
||
- `SKILL.md` 修正被合并的 `find_define` 列表项与 `the your own harness'` 笔误;
|
||
从 `SKILL.md` 链接 `references/query-guide.md`(skills 客户端只按需加载被引用的
|
||
资源,之前该文件永远不会被读取);CLI 段落补充“它是 Node 脚本、没有 Node 时用
|
||
launcher 里的 Electron 运行时”的说明。
|
||
- `CHANGELOG.md` 补上 0.1.26 条目。
|
||
- 全量测试:310 通过 / 0 失败 / 3 跳过(跳过项仍是沙箱禁止 spawn 的三条)。
|
||
|
||
## 21. P1 第一批:安装 / 卸载 / 禁用(2026-09-10)
|
||
|
||
针对“安装与卸载要简单”“升级后要能自动同步”两项:
|
||
|
||
| 内容 | 实现 |
|
||
|---|---|
|
||
| 安装不再依赖索引 | `ra3modxml.installAgentSkill` 改为只读取 `ws.projectRoot`(可为空),无项目时仍可装到用户级目录 |
|
||
| 安装位置更灵活 | 默认 `~/.agents/skills`、Claude Code、项目级 `.agents/skills` / `.claude/skills`、以及自定义文件夹(`<folder>/ra3-mod-xml`) |
|
||
| 安全卸载 | 新增 `readSkillMarker` / `installedSkillStatus` / `uninstallRecordedSkills` / `forgetSkillInstallRecords`:只删除带 marker 且路径匹配的目录,用户替换过的目录跳过并报告 |
|
||
| 禁用实时访问 | 新增 `ra3modxml.disableAgentAccess`:停服务器、清本窗口 instance/endpoint、从幸存实例重建发现文件,保留 skill 与 MCP 配置 |
|
||
| 完整卸载向导 | 新增 `ra3modxml.uninstallAgentIntegration`:可勾选停止实时访问 / 移除 Skill / 移除 MCP 配置 / 删除 launcher,确认后才执行 |
|
||
| MCP 配置可回滚 | 新增 `mcp-install.json` 记录(`installMcpServerConfigToFile`)、`removeMcpServerFromConfigFile`(同时兼容 `mcpServers` 与 VS Code 的 `servers`,空容器自动删除)、`uninstallMcpServerConfigs`(记录 + 常规客户端路径)、`removeLauncher` |
|
||
| 升级自动同步 | 激活时对比 `skill-install.json` 中的版本,有差异才调用 `syncInstalledSkills` 重写;无 marker 的目录不改写 |
|
||
| 本地化 | 新增 55 条 AI Agent 相关字符串到 `l10n/bundle.l10n.json` 与 `zh-cn`(共 210 条/语言),命令标题加入 `package.nls.*` |
|
||
|
||
测试:`agentSetup.test.mjs` 6 项、`agentSkill.test.mjs` 7 项(含 marker 防误删、
|
||
MCP 配置保留其他 server、空容器清理、记录回滚)。
|
||
|
||
## 22. P1 第二批:首装 / 升级提示(2026-09-10)
|
||
|
||
- 新增纯逻辑模块 `src/agent/onboarding.ts`(可单测):
|
||
- `AGENT_FEATURE_VERSION = "0.1.26"`;
|
||
- `shouldOfferAgentOnboarding(state, currentVersion)`:
|
||
首次安装提示一次;从早于 0.1.26 的版本升级提示一次;
|
||
已在带此功能的版本提示过则保持静默;`dismissed` 永久抑制;
|
||
- `compareVersions` 处理 `"0.1" < "0.1.26"`、`"0.1.26-beta" == "0.1.26"`。
|
||
- `extension.ts` 在**第一次索引完成后**(`ws.onIndexUpdate`)弹一次信息提示,
|
||
按钮为 `Enable AI Agent access…` / `Learn more` / `Don't show again`;
|
||
无论用户选择还是忽略,都会把 `informedVersion` 写入 `globalState`,不会重复打扰。
|
||
- 新增 4 条本地化字符串(两种语言各 214 条)。
|
||
- `test/onboarding.test.mjs`:5 个用例覆盖上述规则。
|
||
|
||
### 仍未处理(下一批)
|
||
|
||
- VS Code 原生 `chatSkills` / `mcpServerDefinitionProviders` 接入(免 launcher /
|
||
免手写配置)。
|
||
- Claude/Cursor 路径收敛为数据驱动预设 + `configure --harness`;
|
||
稳定 CLI launcher(免 Node 路径发现)。
|
||
- `mcpServer.ts` 的 `serverInfo.version` 仍硬编码 `0.1.0`;
|
||
`startAgentLocalServer` 仍有并发启动竞态。
|