Skip to content

About

DSH plugin: per-session Unity service pool + instance-level MCP proxy (unity_mcp)

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

34 Commits

Folders and files

Repository files navigation

dsh-unity-pool v2 — 会话级 Unity 服务池 + 实例级 MCP 代理

DSH 插件:解决「同一 DSH 终端(web)里多个会话各连各的 Unity 实例」的问题。

核心思路:mcp-for-unity 的 HTTP 模式把「当前激活实例」按 MCP-Session-Id 隔离——插件为每个 DSH 会话维护独立的 MCP 会话(独立 session id), 再通过 unity_mcp 代理在调用前自动 set_active_instance 到本会话锁定的目标实例。因此同一服务上不同会话可以同时 target 不同实例,互不干扰,无需全局切换/排队。

与官方 Unity MCP 的关系

  • 官方 Unity MCP(CoplayDev/unity-mcp)= 能力层:把 Unity 编辑器翻译成 MCP 工具/资源;
  • 本插件 = 调度层:服务池发现 + 实例发现 + 会话目标实例锁定 + MCP 调用代理;
  • 两者叠加:官方负责「能干 Unity 的活」,插件负责「这个会话连哪个实例」。

数据模型

服务(Service)= 一个 mcp-for-unity server(一个端口,如 8080)
  └ 实例(Instance)= 一个已连接的 Unity 编辑器(Name@hash,如 ProjA@aaaa1111)
会话锁定(Binding)= sessionId → { serviceId, instanceId }
MCP 会话(Session)= 每 DSH 会话 × 服务 一个独立 Mcp-Session-Id(active 实例按 session 隔离)

工作流(对应需求流程)

  1. 会话被告知要处理的服务(8080/8081)或目标工程 → 调 unity_pool_status;
  2. 目标实例不在列表 → unity_pool_scan(服务重探 + 实例重读 + 扫描 scanPorts 端口段发现新服务)→ 再 unity_pool_status;
  3. 列表含多个实例(如 A/B 在 S1、C 在 S2)→ 调 unity_pool_bind(instance="ProjB@bbbb2222") 把本会话目标实例锁定为 B;每次绑定都返回该服务的最新 MCP 工具列表 tools(含 name/description/inputSchema,同服务重复绑定也重拉保持新鲜,拉取失败回退缓存并附 toolsError),可直接据此调用;随时调 unity_pool_status 也可查看本会话已绑服务的工具名速查(tools: {count, names});
  4. 之后所有 MCP 操作走 unity_mcp(tool=..., params=...)——插件自动把本会话的 MCP 会话激活到 B(助手无感),转发 tools/call;请求的工具不在已知列表时自动重拉 tools/list(Unity 可运行时注册/注销自定义工具、manage_tools 可开关工具组);工具名不存在时错误信息会附带当前可用工具名列表(含相似工具名提示),从中选正确的名称重试即可,不要盲猜;查 Unity 编辑器状态(截图/选中项/Console 等)用 unity_pool_state(状态携带开关默认全关,需先开启);
  5. (v0.5.0)绑定后该服务工具自动注册为原生工具 umcp_<工具名>——模型上下文直接可见完整工具清单(含参数),可直接原生调用(等价 unity_mcp 转发;未绑定会话调用报「未锁定目标实例」,先 bind 即可);umcp_* 与 unity_mcp 等价并存(unity_mcp 仍是通用代理兜底,任意工具名可调)。
  6. 另一个会话锁 A 并行工作:per MCP-Session-Id 隔离,两会话各自 target 各自实例,互不干扰;
  7. 用完 unity_pool_unbind 释放。

归档语义(v0.5.1 设计修正):归档 = 本 DSH 会话被归档(session/disposed)→ 插件自动解绑该会话锁定的实例。 实例掉线/域重载对已绑定会话是无感的——绑定保持、不做 probe 主动解绑、也不注入通知;真正的实例离线只在 「调用时」由 unity_mcp/umcp_* 自动重连并在超时(callReconnectTimeoutMs,默认 20s)后才返回失败原因。

关于「切换/排队」:官方 HTTP 模式 active 实例按 session 隔离(见 test_multi_user_session_isolation.py), 所以不需要「服务级全局切换 + 互斥等待」。同一实例被多会话并发调用时由 Unity 侧单线程排队(官方文档所述,性能排队而非错误)。

架构

浏览器端(client)                          Node 宿主(host)
conversation.session.header.utilities       UnityPool(服务池+实例缓存+会话绑定+探活)
  └ Unity 胶囊+面板 ──fetch──>  /unity-pool/api/*(回环)
                                ├ unity_pool_status / unity_pool_scan / unity_pool_bind
                                ├ unity_mcp(代理)──> McpHttpClient(per-session MCP 会话)
                                │                          └ set_active_instance + tools/call
                                └ unity_pool_unbind
                                      持久化:~/.dsh/unity-pool-state.json
  • 宿主 lib/index.js:cordis 插件,inject: ['tools','webServer','systemPrompt'];
  • MCP 客户端 lib/mcp-client.js:streamable-HTTP 薄封装(initialize / resources/read / tools/call,SSE+JSON,per-client 串行);
  • 客户端 lib/client.js:conversation.session.header.utilities 槽(id unity-pool,order 110)。

安装

New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-unity-pool" -Target "C:\Users\PC\dsh-unity-pool" -Force
New-Item -ItemType Junction -Path "C:\Users\PC\dsh-unity-pool\node_modules" -Target "C:\Users\PC\.dsh\profiles\node_modules" -Force
  • ~/.dsh/profiles/web/package.json:dependencies 加 "dsh-unity-pool": "link:C:/Users/PC/dsh-unity-pool",dsh.profile.bundles 加 "dsh-unity-pool"(无 BOM);
  • ~/.dsh/profiles/web/cordis.patch.yml 加配置段(见下);
  • 重启 dsh web 生效;验证 dsh --profile web --dump-config | grep unity-pool。

配置(cordis.patch.yml)

- id: unity-pool
  config:
    services:                 # 服务池:每个 mcp-for-unity server 一个条目
      - id: unity-e-line
        name: 'E线-8080'
        url: 'http://127.0.0.1:8080/mcp'
        # v0.5.8 可选:服务离线时 unity_pool_launch 用它拉起 server(不配则要求服务已在线/Unity 侧 Auto-Start 可用)
        startCommand: ['uvx', '--offline', '--from', 'mcpforunityserver==10.1.0', 'mcp-for-unity', '--transport', 'http', '--http-url', 'http://127.0.0.1:8080']
      - id: unity-8081
        name: 'Unity-8081'
        url: 'http://127.0.0.1:8081/mcp'
    probeIntervalMs: 8000     # 探活+实例发现间隔(ms)
    probeTimeoutMs: 3000      # 单次 MCP 请求超时(ms)
    scanPorts: [8080, 8081, 8082, 8083, 8084, 8090]   # unity_pool_scan 自动扫描的端口段
    scanPortTimeoutMs: 0      # 单个候选端口(握手+实例发现)总时长上限(ms;0/缺省 = max(2000, probeTimeoutMs×2)):端口上挂着「只回响应头不收尾」的服务时跳过该端口而非卡死
    scanTimeoutMs: 20000      # unity_pool_scan 工具/接口总时长上限(ms):超时返回已完成部分 + timedOut 标记,绝不出现「工具调用一直运行中」
    autoAssign: true          # 未指定实例时自动分配未被其他会话锁定的实例
    enforceExclusive: true    # 仅在显式传 force=false 时生效(并行开发是正常用法,force 默认 true,默认不拒绝并行绑定)
    connectHint: '调用 unity_mcp(tool=..., params=...) 代理 MCP 工具调用'
    busyWaitEnabled: true     # unity_mcp 调用前探测 Unity 忙状态(编译/刷新/进度条)并自动等待
    busyMaxWaitMs: 10000      # 忙时等待总时长上限(ms),默认 10 秒
    busyWaitIntervalMs: 500   # 忙时等待的探测间隔(ms)
    autoUnbindOnArchive: true # 会话归档(DSH session/disposed)时自动解绑该会话锁定的实例;实例掉线/域重载对绑定会话无感(不主动解绑、不注入通知)
    callReconnectTimeoutMs: 20000 # 调用时实例离线的重连/等待时长(ms,默认 20s):unity_mcp/umcp_* 路由到实例时给足超时让官方服务端的会话重连等待完成,超过才判定实例离线并返回失败原因
    # ---- 启动 Unity 编辑器(v0.5.8)----
    launchEnabled: true           # 允许 unity_pool_launch / 客户端「启动 Unity」入口
    projects:                     # 工程清单:name = 调用/界面上的名字,path = 工程绝对路径(version/serviceId 可省)
      - { name: '_P2_前后挡A岛', path: 'C:\Users\Public\Project\_P2_前后挡A岛' }
      - { name: '_P4_总装车间E线', path: 'C:\Users\Public\Project\_P4_总装车间E线', serviceId: 'unity-e-line' }
    editorPaths: []               # 额外显式 Unity.exe 路径(优先于 editorRoots 扫描,可写多个版本)
    editorRoots:                  # Unity Hub 编辑器安装根目录(默认 Windows: C:\Program Files\Unity\Hub\Editor)
      - 'C:\Program Files\Unity\Hub\Editor'
    allowVersionMismatch: true    # 工程版本与已装版本不一致时用已装版本打开并标 mismatch(false = 直接报错)
    allowAnyProject: true         # 是否允许 unity_pool_launch(projectPath=...) 启动清单外工程
    launchLogDir: '~/.dsh/unity-pool-launch' # 启动日志目录(每个工程一个 -logFile)
    launchWaitReadyMs: 120000     # 启动后等待实例出现的上限(ms,默认 120s);超时返回 ready:false
    launchPollIntervalMs: 2000    # 等待实例的轮询间隔(ms)
    launchArgs: []                # 追加到 Unity 命令行的额外参数(如 -force-vulkan)
    launchDialogCheckEnabled: true # 启动等待期间检测 Unity 模态窗(版本不匹配/保存确认等),检测到提前返回选项
    launchDialogCheckAfterMs: 8000 # 启动后多久开始查模态窗(ms)
    dialogWatchEnabled: true       # 后台周期扫描模态窗(有 Unity 运行时每 dialogPollIntervalMs 查一次,结果进 view.dialogs)
    dialogPollIntervalMs: 10000    # 模态窗扫描间隔(ms)
    autoDismissDialogs: []         # 自动处理规则:{match:'标题/正文包含', option:'Continue'} 或 {match, close:true}(默认空=不自动点)
    autoDismissOnMCPFailure: false # MCP 调用掉线时是否按上面规则自动处理模态窗(默认关)
    serverStartWaitMs: 30000      # 拉起 services[].startCommand 后等待服务监听的时长(ms;超时返回 server.alive:false + 日志)
    closeWaitMs: 15000            # unity_pool_close 优雅退出等待上限(ms;超时返回 stillRunning,需 force=true 强杀)
    closeStopsServer: true        # 关闭后停掉「在线且无任何实例」的服务(含 uvx/python 子进程树与 Unity 侧 cmd /k 终端;false=保留)
    # ---- 原生工具注册(v0.5.0,Claude 式工具清单)----
    nativeToolsEnabled: true   # 绑定成功后把该服务的 MCP 工具动态注册为原生工具(umcp_<工具名>),模型上下文直接可见可调
    nativeToolSchema: compact  # 参数 schema 形态:compact=基础标量类型+description、复杂参数不展开(省上下文,默认);full=完整 inputSchema(与 Claude 注册 MCP 工具一致,参数校验最全但上下文开销大)
    # ---- 错误日志携带(v0.5.2,默认开)----
    errorWatchEnabled: true    # 每次 unity_mcp/umcp_* 调用「前后各检查一次」Console:期间新增的错误(编译错误/运行时报错)随本次调用结果返回
    errorWatchCount: 5         # 单次最多携带的新增错误条数(取新增批次里最早的 N 条)
    errorWatchMaxChars: 2000   # 单条错误文本最大字符数(超出截断并标注)
    errorWatchFetchMax: 50     # 拉取明细时最多读取的 Console 条数(防超大批次拖慢)
    errorWatchStacktrace: true # 明细是否带堆栈(运行时错误靠堆栈定位;单条仍受 errorWatchMaxChars 截断)
    # ---- 状态携带(v0.4.0,默认全关)----
    stateEnabled: false           # 总开关:每次发出指令时在上下文携带 Unity 状态快照
    stateGameScreenshot: false    # Game 视图截图(PNG 落盘 stateDir,上下文给路径)
    stateSceneScreenshot: false   # Scene 视图截图
    stateSelection: false         # Hierarchy/Project 当前选中项
    stateUiSnapshot: false        # 选中物体 ui-snapshot 结构快照(需实例注册 ui_snapshot 工具)
    stateSerialized: false        # 选中物体序列化字段内容(防超长截断)
    stateConsoleAll: false        # Console 全文(最近 stateConsoleCount 条)
    stateConsoleSelected: false   # Console 当前选中条目内容
    stateRefreshMs: 3000          # 状态采集间隔(ms)
    stateScreenshotMaxRes: 640    # 截图最长边分辨率(px)
    stateDir: '~/.dsh/unity-pool-state' # 截图 PNG 落盘目录
    stateMaxChars: 8000           # 通用文本项(选中项/序列化字段)最大字符数
    stateSnapshotMaxChars: 4000   # ui-snapshot 最大字符数
    stateConsoleMaxChars: 6000    # Console 文本最大字符数
    stateConsoleCount: 50         # Console 全文读取条数
    stateFirstTurnWaitMs: 4000    # 冷启动补齐(v0.5.6):装配阶段发现「开了状态但缓存为空」时有界等待一次采集再注入(新会话/插件重启后首条消息也带状态);0=关闭
    stateCollectCooldownMs: 10000 # 冷启动补齐冷却(ms):采集失败(Unity 离线/繁忙)后这段时间不再重复等待,避免每步都卡

开关位置(方便按需切换):客户端在会话头部「Unity」胶囊(点开面板末段)与输入框上方(conversation.input.dock 槽,聊天输入框正上方一条)各有一组状态携带开关(总开关 + 8 项子开关),运行时点击即切(走 POST /api/state-switch,无需重启);开关按会话独立(v0.4.2)——每个会话自己的开关(stateSwitchesBySession 按 sessionId 持久化,重启后本会话恢复上次设置),切换只影响本会话的采集/注入;未绑定 Unity 也可切换(预配置,灰显提示「未绑定 Unity,绑定后生效」,绑定后立即生效;子开关在总开关关闭时禁用);开启总开关后每次发出指令自动注入最近一次快照——需要传状态的指令前开一下、不需要时关掉即可。旧版全局平铺 stateSwitches 自动迁移为全局默认层(所有会话的未覆盖项继承它)。

绑定入口两处(v0.5.3):头部「Unity」胶囊只在已有会话头部时存在,新会话(首条消息前)没有头部槽 → 绑定入口另落在输入框上方 dock 左端的绑定芯片(点开 = 服务池实例列表 + 锁定/解绑/扫描),因此新会话也能提前绑定实例;两处都按默认绑定(并行开发同一实例是正常用法,不再先试探 force:false 再重试)。

启动/关闭入口(v0.5.8,2026-10-18 按用户要求移到右上角胶囊弹窗):点开头部「Unity」胶囊后,弹窗上部即「启动 Unity」区——工程清单(名称/版本/是否带 MCP 包/运行中)。未运行的行点「启动」= POST /api/launch → 拉起编辑器并等实例出现后自动绑定;运行中的行显示「关闭」= POST /api/close(默认优雅退出,超时会提示需 force)。首次导入可能 1~3 分钟,只有被点击的那个工程显示「启动中…」(v0.5.8 修复:以前所有行都会显示)。弹窗里还有「模态窗」:自动显示后台扫描到的 Unity 原生对话框(版本不匹配/保存场景/确认框),也可点「检查」手动刷新;点选项即选择,点「关闭窗口」直接关闭(WM_CLOSE)。输入框 dock 只保留绑定芯片与状态开关。未配置 projects 时显示配置指引(也可让 Agent 用 unity_pool_launch(projectPath=...))。

两处 UI 同源(v0.4.1):头部胶囊、输入条 dock 的开关与绑定状态共用客户端共享状态源(模块级 pub-sub store,按 sessionId 分槽)——任一入口切换/绑定/解绑立即广播,另一入口即时同步;胶囊另有 5s 轻轮询兜底(agent 工具调用 unity_pool_bind/unity_pool_unbind、其它标签页的变化 ≤5s 自动反映到胶囊,无需手动刷新)。

Agent 工具

工具 作用
unity_pool_status 服务池 → 每服务实例列表(Name@hash/hash/是否本会话激活 + instancesValid/offlineStreak)+ 本会话锁定 + 最近归档自动解绑 lastAutoUnbind;已绑定时附带该服务最新工具名速查 tools: {count, names}
unity_pool_scan 服务重探 + 实例重读 + 扫描端口段发现新服务;全程有预算(v0.5.5)——单端口 scanPortTimeoutMs、整体 scanTimeoutMs(默认 20s),超时的端口记入返回的 skippedPorts,整体超时返回 timedOut:true + 已完成部分(不会永远「运行中」)
unity_pool_bind 锁定本会话目标实例(instance=Name@hash/hash 前缀 / serviceId / 自动分配;force 覆盖排他);每次绑定都返回该服务最新 MCP 工具列表 tools(name/description/inputSchema)+ toolsCount(同服务重复绑定也重拉保持新鲜;拉取失败回退上次缓存并附 toolsError,不阻断绑定);绑定后该服务工具自动注册为原生工具 umcp_<工具名>(v0.5.0)——模型上下文直接可见全部工具名/描述/参数并原生调用(等价 unity_mcp 转发,未绑定会话调用报「未锁定目标实例」);注意:工具列表为服务级并集(同服务多工程实例的自定义工具合并列出,个别工具可能不属于当前实例,调用失败即说明该实例未注册)
unity_mcp 代理 MCP 工具调用(自动 set_active_instance 到目标实例 → tools/call 转发);工具不在缓存列表时自动重拉 tools/list;工具名不存在时错误信息附带当前可用工具名列表(含相似工具名提示),选正确名称重试即可;Unity 编译/刷新期间自动等待(忙时探测最长 busyMaxWaitMs,默认 10s;可 busyWaitEnabled:false 关闭);调用失败返回附带编辑器状态 editorState(isCompiling/isUpdating/progressCount,便于判断是否忙碌所致);返回 text(image/audio/resource 内容块以 [image: ...] 占位,不静默丢弃);调用前后各检查一次 Console(v0.5.2)——期间新增的错误(编译错误/运行时报错)随本次结果返回(结构化 errorWatch + 文本附在 text 末尾,默认前 5 条、单条 2000 字符,同一批只返回一次)
unity_pool_launch 启动 Unity 编辑器并等就绪、自动绑定本会话(v0.5.8):project(配置清单名)或 projectPath(任意工程绝对路径);解析已装版本(editorPaths/editorRoots,版本不一致默认用已装版本并标 mismatch)、检查 Temp/UnityLockfile 防重复打开、以 -projectPath/-logFile 拉起、轮询服务池直到该工程实例出现(默认最长 launchWaitReadyMs=120s)→ 自动 bind 并带工具列表;返 pid/logFile/instance/binding,未就绪返 ready:false + 指引;可选 editorPath/version/serviceId/waitMs/bind/waitReady/force。注意:启动编辑器抢物理资源,Agent 自行启动前必须先写当日排期台账(_mini: C:\Users\Public\Project\_schedule\YYYY-MM-DD.md;_pc: E:\dsh\schedule\YYYY-MM-DD.md),单次 ≤30min
unity_pool_close 关闭 Unity 编辑器(v0.5.8 配套):project / projectPath / pid;默认优雅退出(Windows taskkill 不带 /F、*nix SIGTERM),等待 closeWaitMs=15s,超时返 stillRunning + 提示(可能有未保存改动),确认可丢弃后传 force=true 强杀;工程未运行时会顺手清理陈旧 Temp/UnityLockfile;关闭后若服务上已没有任何 Unity 实例,默认把 MCP 服务一并停掉(stopServer:false 保留)——包括本插件拉起的 uvx/python 子进程树、端口占用者,以及 Unity 侧「Start Server」留下的 cmd /k「MCP Server」终端窗口(Library/MCPForUnity/TerminalScripts/mcp-terminal.cmd,这正是「启动的终端残留」来源)。运行判定修复:以前只看锁文件存在(崩溃残留会被当成运行中),现在锁文件必须被独占持有才算运行,或本插件启动记录里的 PID 仍活着
unity_pool_dialog 查看/选择/关闭 Unity 模态窗(v0.5.8 增强,Windows):action=list(默认)枚举 Unity 进程的 Win32 原生 #32770 对话框(版本不匹配、保存场景、确认框),返回 title / message(Static 文本)/ options(序号+文案+HWND);action=click + option(序号或文案,如 Continue / Don't Save / OK)用 BM_CLICK 点击;action=close 用 WM_CLOSE 直接关闭窗口;都返回 remaining。可用 project/projectPath/pid 限定范围。模态窗会阻塞 Unity 主线程导致 MCP 暂时掉线:后台默认每 10s 扫描(view.dialogs,客户端弹窗自动显示),启动卡导入/关闭返回 stillRunning/unity_mcp 掉线失败都会自动附带 dialogs;autoDismissDialogs 可配置按标题/正文自动选择或关闭
unity_pool_unbind 释放锁定 + 关闭本会话 MCP 会话(同时清掉本会话的错误携带游标,重绑后重建基线)
unity_pool_state 查看/刷新本会话的 Unity 状态携带快照(v0.4.0):立即采集一次并按开关返回各项状态(截图文件路径/选中项/序列化字段/Console)+ view.state(开关值与缓存摘要);refresh:false 只读缓存

unity_mcp 参数:tool(mcp-for-unity 工具名,如 manage_scene / manage_gameobject / manage_camera / read_console)、params(工具参数对象)、instance(可选临时覆盖)。

HTTP API(回环,仅 127.0.0.1/localhost)

  • GET /unity-pool/api/status?sessionId=<id> —— 服务/实例/绑定视图;
  • GET /unity-pool/api/config —— 池配置与提示;
  • POST /unity-pool/api/scan {sessionId} —— 重探+扫描;
  • POST /unity-pool/api/bind {sessionId, instance?, serviceId?, force?} —— 锁定目标实例(每次绑定都返回附带 tools);
  • POST /unity-pool/api/unbind {sessionId} —— 释放。
  • GET /unity-pool/api/state?sessionId=<id> —— 当前状态携带缓存与视图;
  • POST /unity-pool/api/state-refresh {sessionId} —— 立即重新采集一次状态;
  • POST /unity-pool/api/state-switch {sessionId, key, value} —— 运行时切换状态开关(key: stateEnabled / stateGameScreenshot / stateSceneScreenshot / stateSelection / stateUiSnapshot / stateSerialized / stateConsoleAll / stateConsoleSelected / stateConsoleError,无需重启)。
  • GET /unity-pool/api/launch —— 启动能力摘要(工程清单 + 最近启动记录,同 view.launch);
  • POST /unity-pool/api/launch {sessionId, project? | projectPath?, editorPath?, version?, serviceId?, waitReady?, waitMs?, bind?, force?} —— 启动编辑器并等实例就绪、自动绑定(v0.5.8;客户端「启动 Unity」走这里)。
  • GET /unity-pool/api/dialog?projectPath=&pid= —— 枚举 Unity 原生模态窗(title/message/options);
  • POST /unity-pool/api/dialog {sessionId, action:'list'|'click'|'close', projectPath?, pid?, hwnd?, option?} —— 查看/选择/关闭模态窗(Windows)。

测试

node "C:\Users\Landrom\dsh-unity-pool\scripts\smoke-test-v2.mjs"   # 279 项:mock mcp-for-unity ×2 + 实例发现/会话锁定/排他/会话隔离/代理转发/动态工具重拉/跨服务重拉/重复绑定带工具/未知工具错误附工具名+相似提示/view 工具名速查/tools 失败回退缓存/图片占位/53 工具全量对照/scan/持久化/工具/HTTP/忙时等待/失败附状态/探测失败保守等待/归档自动解绑/归档解绑动态通知/状态携带(默认全关/全项采集/截图落盘/防超长/开关切换/单项失败/context 注入/HTTP)+ 状态开关 per-session(隔离/持久化/迁移/缺 sessionId 拒绝)+ v0.5.0 原生工具注册(绑定注册 umcp_*/compact 标量保留+复杂不展开/full 完整 schema/未绑定报错/已绑定转发成功/view.nativeTools 摘要/stop 注销/跨服务同名接管/解绑不注销/nativeToolsEnabled=false 禁用)+ v0.5.2 错误日志携带(首次建基线/新增带回/明细进 text/编译错误识别/同批只报一次/超上限只显前 5 条/单条 2000 字符截断/Console 清空不误报/编译完成无错误/编译+编译错误/view 摘要/解绑重建基线/自定义上限/开关关闭零探测/计数不可用降级)+ v0.5.5 扫描卡死回归(响应体永不结束的挂起服务/挂起端口/串行链自愈/工具级预算内返回)+ v0.5.6 冷启动补齐回归(绑定前无缓存/同步求值为空串/装配瀑布注册/装配阶段补齐/回合缓存/热缓存零等待/未开状态不注入/失败有界返回/冷却不重复等待/开关可关闭)+ v0.5.8 启动编辑器(编辑器版本解析/启动参数/等待实例自动绑定/view.launch/重复打开保护/未就绪路径/版本 mismatch/allowAnyProject/lockfile 判定/HTTP launch)(UNITY_POOL_LIB 环境变量可指向被测 lib)

变更日志

  • 0.5.8 启动 Unity 编辑器能力(Agent 工具 + 客户端入口):新增 unity_pool_launch 工具与 POST /api/launch,一条调用完成「拉起编辑器 → 等 MCP 实例出现 → 自动绑定本会话」(用户需求「加个启动 Unity 编辑器的能力」)。实现要点:① 工程来源两路——projects 清单(按 name 启动,view.launch.projects 可见路径/版本/是否带 MCP 包/是否运行中)或 projectPath(任意工程,allowAnyProject:false 可禁);② 编辑器解析——editorPaths 优先,否则扫描 editorRoots(默认 C:\Program Files\Unity\Hub\Editor\<版本>\Editor\Unity.exe,macOS 兼容),按工程 ProjectVersion.txt 精确匹配;版本不一致默认用已装最新版打开并标 mismatch(allowVersionMismatch:false 可直接报错);③ 防重复打开——本插件启动记录里的进程仍活着、或工程 Temp/UnityLockfile 存在 → 返回 alreadyRunning:true 并尽量直接绑定现有实例(force:true 才强制再开);④ 启动——-projectPath <工程> -logFile <launchLogDir>/<工程>-<时间>.log + launchArgs,detached + unref(不占宿主进程);⑤ 就绪判定——每 launchPollIntervalMs 执行一次整池 probe(带既有预算),按「实例名 == 工程名 → 名称互相包含 → 启动后新出现且仅一个实例」匹配,默认最长 launchWaitReadyMs=120s,超时返回 ready:false + 后续指引(编辑器通常仍在导入);⑥ 自动绑定——就绪后调既有 bind(force 默认 true),返回 toolsCount;⑦ 可观测——view.launch / GET /api/launch 给出工程清单与最近 10 条启动记录(pid/是否存活/是否就绪/日志路径),客户端右上角胶囊弹窗新增「启动 Unity」区与工程清单(输入框 dock 只保留绑定/开关);⑧ 提示——工程没有 mcp-for-unity 时明确告知「启动后不会出现在服务池」;检测兼容两种安装方式:Packages/manifest.json 声明(mcpSource:'packages')或源码在 Assets/Plugins/CoplayDev/MCPForUnity 等(mcpSource:'assets';老岛 P1/P2/P3/P7 即此方式,PC 新版已进 Packages 并本地化);工具描述与系统提示写清「Agent 自行启动前必须先写资源排期台账」;⑨ 服务自愈(可选)——目标服务全离线且配置了 services[].startCommand 时,launch 先按命令拉起 mcp-for-unity(日志落 launchLogDir/server-<service>-<time>.log),并在 serverStartWaitMs 内等服务监听;子进程 env 会去掉 no_proxy 里 httpx 无法解析的 [::1](实测本机 DSH 进程 env 带该项会让 uvx mcp-for-unity 直接抛 InvalidURL: Invalid port: ':1]'),Unity 侧 Auto-Start 被代理 env/URL 安全策略挡住时也能用;服务已在线则跳过。⑩ 关闭编辑器(配套能力)——unity_pool_close 工具 + 弹窗运行中行的「关闭」按钮 + POST /api/close:默认优雅退出(Windows taskkill 不带 /F / SIGTERM),等待 closeWaitMs(默认 15s),超时返回 stillRunning 需显式 force=true 强杀;工程未运行时会清理陈旧 Temp/UnityLockfile;关闭后若服务上没有任何实例,默认把 MCP 服务一并停掉(uvx/python 子进程树、端口占用者,以及 Unity 侧 cmd /k「MCP Server」终端窗口——「启动的终端残留」就是它),stopServer:false 可保留。同时修复「P4 没开编辑器却显示运行中」:projectRunning 不再只看锁文件存在,改为「本插件启动记录 PID 存活」或「锁文件被独占持有」(能 r+ 打开 = 陈旧锁,自动清理);findEditorPids 通过 Win32_Process 命令行的 -projectPath 定位不由本插件启动的编辑器 PID。⑪ 模态窗增强(v0.5.8)——unity_pool_dialog 工具 + GET/POST /api/dialog + 客户端「模态窗」检查/选项:枚举 Unity 进程的 Win32 原生 #32770 对话框(PowerShell + Add-Type EnumWindows/EnumChildWindows),返回 title/Static 正文/Button 选项,点击用 BM_CLICK;启动等待超过 launchDialogCheckAfterMs 检测到模态窗会提前返回 dialogBlocked:true + dialogs(不再傻等 180s),关闭优雅超时也会附带 dialogs(如「Save changes?」);后台默认每 dialogPollIntervalMs=10s 扫描一次(只在有 Unity 运行时真调 PowerShell),结果进 view.dialogs 并在客户端弹窗自动显示;action=close 用 WM_CLOSE 关闭窗口;autoDismissDialogs 可按标题/正文自动选择/关闭,autoDismissOnMCPFailure 在 MCP 掉线时按规则自动处理;unity_mcp/umcp_* 掉线失败会先查模态窗,把「模态窗阻塞主线程导致 MCP 掉线」+ 选项写进失败文本。另修 PowerShell 输出编码:Windows 上默认按控制台代码页(GBK)输出,Node 按 UTF-8 解码会把中文标题/按钮变成乱码(实测 DSH 模态窗测试 → DSH ģ̬������),导致按中文标题匹配的 autoDismissDialogs 失效——现在所有 PowerShell 命令执行前强制 stdout UTF-8。真机受控验证:EditorUtility.DisplayDialog 弹出后,监视 7.6s 检出(hwnd/中文标题/&OK/&Cancel),BM_CLICK 点 OK 关闭、remaining=[],被阻塞的 MCP 调用随后返回 ok。测试:smoke-test-v2 扩到 279 项全过(新增 39 项:解析版本/启动参数/实例出现后自动绑定/view.launch/重复打开保护/未就绪路径/未就绪记录可见/服务 startCommand 拉起/服务未监听提示/服务已在线不重复拉起/子进程 no_proxy 清理/MCP 的 Packages 来源/Assets 来源/Assets/UnityMCP/Log 不误判/陈旧锁不判运行且自动清理/独占锁判运行(PowerShell FileShare.None 实测)/close 优雅关闭/close 未运行清理/close 优雅超时/close force 强杀/close 停空闲服务/stopServer=false/服务离线仍清残留终端/模态窗枚举与选择/启动检测到模态窗提前返回/关闭超时附带模态窗/后台扫描缓存/autoDismissDialogs 自动选择/掉线失败附带模态窗/PowerShell 输出 UTF-8(中文标题不乱码)/版本 mismatch/严格版本报错/allowAnyProject/非 Unity 工程拒绝/工具描述/GET /api/launch/config),client VM 冒烟扩到 60 项全过(弹窗启动入口/只有被点的工程显示启动中/POST /api/launch 带工程名/就绪提示/运行中显示关闭/POST /api/close/关闭提示/检查模态窗与选项/选择走 /api/dialog/关闭窗口按钮/dock 不再有启动入口;顺带修正状态子开关数量断言 8→9)。⚠️ 本机配置需在 cordis.patch.yml 的 unity-pool 段补 projects 清单(已补 6 个工程),并重启 dsh web(host 端);客户端刷新页面即生效。2026-10-18 用户更正并调整:P1/P2/P3/P7 的 MCP 在 Assets(已纳入检测),启动入口按用户要求从输入框 dock 移到右上角胶囊弹窗。

  • 0.5.7 修复「同一 MCP 服务上并存两个 Unity 实例时,本会话绑定失效、所有 MCP 调用被拦」(2026-09-16 实测复现)。根因:实例选择此前只依赖官方 session-scoped state(set_active_instance 写入、按 Mcp-Session-Id 存),而插件仅在本地镜像 client.activeInstance ≠ 绑定实例时才去激活;服务端 MCP 会话一旦被重建(服务重启 / 会话被清理 / 编辑器域重载),本地镜像仍是陈旧的「已激活」→ 跳过激活 → 在没有选择的新会话上直发 tools/call。该服务上只有 1 个实例时官方会自动选中(把问题掩盖住),一旦并存 2 个实例就被官方守卫拦死:Multiple Unity instances are connected. Call set_active_instance … Available: [...]。修复三处:

    • ① 每调用路由(主修复):每次 tools/call 都携带 unity_instance=<绑定实例>,由官方 UnityInstanceMiddleware 在本次调用上直接解析实例,不依赖会丢失的 session state(实测:全新 MCP 会话、不做任何 set_active_instance,带该参数也能正确路由;read_console/manage_editor/batch_execute 等均接受)。老服务端不认识该参数时自动降级为只用会话状态(判定后只降级一次,不影响旧部署)。
    • ② 本地镜像与 MCP 会话同生共死:activeInstance 改为「镜像值 + 记下它属于哪个 session」,会话被重置/失效即作废(sessionInvalid 时显式清空),不再出现「本地以为已激活、服务端其实没有」。
    • ③ 不再伪造成功 + 不静默改投:unity_mcp(tool='set_active_instance') 此前短路返回 active instance set to X(让"选择没生效"看起来像成功,本次缺陷最误导人的一环),现在真转发、真结果;遇到官方实例选择守卫时显式重建会话内选择并重试一次,仍失败则返回 success:false + 【路由未生效】 说明(明确「本次调用未在任何实例上执行」,绝不改投其它实例)。附带:绑定结果不再无声吞掉激活失败(新增 activateError);陈旧绑定(服务不在池中)给出可读原因;绑定到扫描发现的服务(scan-N)不再在插件热重载后被静默丢弃。
    • 验证:scripts/smoke-test-v2.mjs 240 项全过(0 失败,含单实例/多实例/绑定/排他/状态携带全量回归);真机双实例(P2 2022.3.62f2c1 + P4 2022.3.62f3c1 同挂 8090)实测:① read_console 正常返回;② 绑 P4 后 execute_code 回读 2022.3.62f3c1 | G:/Project_AAA/WuLing/_P4_总装车间E线/Assets,绑回 P2 得 2022.3.62f2c1 | …/_P2_前后挡A岛/Assets;③ 故意 DELETE /mcp 杀掉本会话的服务端 MCP 会话后,下一次调用自动恢复(修复前该状态下每次调用都返回 instance_selection_required)且不再伴随 10s 忙等待;④ 指定不存在的实例 → success:false + Instance '…' not found. Available: […]…,不静默改投。
  • 0.5.6 修复「新会话绑定后开启状态、发消息没注入上下文」:根因是注入路径的同步/异步错配——systemPrompt.context.text 是同步函数(harness 契约:text: string | ((context) => string)),只能读已有缓存;而状态采集是异步的(MCP 往返 1~3s)。新会话 / 插件热重载 / 刚打开开关时缓存为空 → 首回合求值拿到空串,且旧代码还把空串写进「按回合缓存」→ 整个回合(含后续 step)都没有状态块。观察到的现象就是「开了状态、发消息,上下文里没有 Unity 状态」。修复:

    • ① 新增 system-prompt/assemble 装配瀑布监听(唯一可 await 的装配钩子):条目为空且本会话启用状态携带时,有界等待一次采集(stateFirstTurnWaitMs,默认 4s;冷却 stateCollectCooldownMs,默认 10s)后就地替换该 context 条目 → 首条消息即带状态;缓存已热时零开销(直接返回)。
    • ② collectState 防重入改为返回同一个在飞 Promise(原来返回 null):消息/开关触发的采集正在跑时,装配阶段能等它结束,而不是误判「没在采」。
    • ③ stateTextForTurn 不再缓存空文本:万一补齐超时,同回合后续 step 仍能拿到采集完成后的状态块(第二道保险)。
    • ④ bind 后/解绑时的既有行为保持:绑定后立即采集一次;解绑清掉冷启动冷却,重绑可立即补齐。
    • 验证:smoke-test-v2 240 项全过(新增 11 项:绑定前无缓存、同步求值为空串(修复前形态)、装配瀑布注册、装配阶段补齐状态块、预算内完成、写入回合缓存、缓存已热零等待、未开状态携带不注入不等待、采集失败有界返回、冷却期内不重复等待、stateFirstTurnWaitMs=0 关闭)。另用带 1.5s 往返延迟的 mock 实测:修复前装配文本为空串,修复后瀑布等待 ~3s 并注入状态块。
  • 0.5.5 修复 unity_pool_scan 卡死(工具调用永远「运行中」):根因在 MCP 客户端——_request 在 fetch 拿到响应头后就 clearTimeout,而超时器本该覆盖到响应体读完;当某个端点只回响应头、响应体永不结束(streamable HTTP 的 SSE 保活/长连接、或端口上挂着非 MCP 服务)时 res.text() 永久挂起,该 client 的串行链被一个永不 settle 的任务占住,于是 listInstances/probe/unity_pool_scan 之后的所有调用全部排队等死(unity_pool_scan 就是「一直运行中」)。修复:① _request 的 AbortController 计时器活到响应体读完(finally 统一清理),超时错误区分「请求失败/响应体未结束」并报真实预算;② probeService 增加总预算(max(5000, probeTimeoutMs×6))并在超时后丢弃该服务的发现客户端(自愈,下次探测重建,不留毒化链);③ scan() 每个候选端口有独立预算(scanPortTimeoutMs,缺省 max(2000, probeTimeoutMs×2)),超时端口跳过并记入 lastScanSkipped;④ unity_pool_scan 工具与 POST /api/scan 共用 runPoolScan,整体预算 scanTimeoutMs(默认 20s)——超时返回已完成部分 + timedOut:true,保证任何情况下都在预算内返回;⑤ probe() 改 Promise.allSettled(单服务异常不拖垮整池、不产生 unhandled rejection);⑥ 探活 GET 显式取消响应体(不占连接)、发现客户端按 URL 变化重建。smoke 扩到 229 项全过(新增 9 项回归:挂起服务 probe 不挂/标记超时/丢弃客户端、挂起端口 scan 跳过、首请求挂起后自愈、工具级预算内返回 + skippedPorts)。

  • 0.5.4 新增 Console 错误状态携带开关:新增第 8 项子开关 stateConsoleError(面板/输入框 dock 均加「Console 错误 / Console错」项,默认关)——状态携带时只采集 read_console types=['error'] 的 error 级条目(复用 stateConsoleCount/stateConsoleMaxChars 与最新优先截断),让注入的 Console 一眼看到错误而非全文噪音;为 stateConsoleAll/stateConsoleSelected 之外的独立开关,互不影响。(2026-09-02 修正:Unity「缺脚本/缺 Behaviour」消息虽被 mcp-for-unity 标为 Error,但 Console 里是黄色警告,已按 Unity 固定文案 UNITY_MISSING_SCRIPT_RE 剔除,只保留真 error 级。)

  • 0.5.4 修复 Console 状态携带「只见旧不见新」:状态携带的 Console 全文(stateConsoleAll)此前把 read_console 返回的旧→新条目 join 后按 slice(0, maxChars) 保留开头(最旧)——一旦最近 N 条超出 stateConsoleMaxChars(默认 6000;长的 JSON 请求/响应日志极易超),最新日志被整段裁掉。于是注入的 Console 既不是全程日志(count 只回最近 N 条)也不是最新日志——「不是新的也不是旧的」。新增 Console 专用 UnityPool.truncateConsole(entries, maxChars, label):按行从**尾部(最新)**向前累加,尽量整条保留;单条超预算时掐该条尾部;前缀标注「…[Console 超预算:显示最新 X 条,省略 Y 条更早日志(原 N 条 / M 字符)]」。consoleAll 改为捕获条目数组后用 truncateConsole(truncate 通用助手用于选中项/序列化字段,保持不变)。

  • 0.5.3 新会话可提前绑定 + 修复「点锁定必报 enforceExclusive」:① 绑定入口下沉到输入框上方的 dock——新会话(首条消息之前)根本没有会话头部槽,头部「Unity」胶囊不渲染,于是无法提前绑定实例;现在 dock 左端多一个绑定芯片(未绑定显示「绑定 Unity」、已绑定显示实例名 + 存活点),点开是服务池实例列表(锁定/解绑/扫描),与头部面板经共享 store 即时同源。② 面板「锁定」按钮此前先发 force:false 试探、再按错误文案 /已被会话/ 自动 force 重试,而 v0.5.1 把拒绝文案改成了「正被其他会话并行使用(enforceExclusive…)」→ 匹配失效 → 用户点「锁定」只看到报错、根本绑不上;并行开发同一实例本来就是正常用法,现在两处入口都直接按默认绑定(force 交给服务端默认 true),不再试探重试。client VM 冒烟扩到 45 项(含 dock 绑定入口开合/锁定/解绑/扫描、bind 请求体不带 force、两处即时同源)。

  • 0.5.2 错误日志携带(编译错误 + 运行时新增错误,同一管线):每次 unity_mcp/umcp_* 调用在转发前后各检查一次 Unity 状态——前检查复用忙等待循环的探测(零额外往返),后检查一次 execute_code;探测顺带取 Console 计数(LogEntries.GetCountsByType 反射,取不到则降级为只跟踪编译跃迁)。错误计数相对上次上报游标增长 → 本次调用结果直接带回新增错误明细(read_console types:['error'],默认带堆栈):结构化字段 errorWatch(newErrorCount/compileDetected/compileErrors/entries)+ 文本附在 text 末尾(模型直接可见);默认前 5 条、单条 2000 字符截断(errorWatchCount/errorWatchMaxChars)。去重语义:游标推进 = 同一批错误只在一次工具调用中返回一次;会话内首次调用只建立基线(不回溯历史错误);Console 被 Clear(计数回落)游标回退不误报;解绑→重绑重建基线;检测到编译完成但无新增错误时回一行「编译完成,Console 无新增错误」(可当编译通过确认)。新增配置 errorWatchEnabled/errorWatchCount/errorWatchMaxChars/errorWatchFetchMax/errorWatchStacktrace,view.rules+view.errorWatch+HTTP /api/config 同步暴露;调用后探测状态另回传 editorStateAfter。明细走 read_console format=detailed([Error] 文案 (at 文件:行) + 堆栈;errorWatchStacktrace:false 时改用 plain 只回文案)。漂移校正:read_console 回的是「最新 N 条」,读明细期间 Console 又冒出错误会把窗口整体后移(真机实测漏掉本批最早那条)——改为多取余量条 + 读完再探一次当下计数,按全局下标把窗口对齐回本批起点,漂移期间新增的错误一并纳入本批(不留到下次重复刷)。smoke 扩到 207 项全过(含降级/清空/上限/堆栈开关/读取竞态/关闭开关等 26 项新用例)。

  • 0.5.1 dock 对齐输入框修复:输入框上方两条 dock(⚡ 转译 与 Unity 状态携带开关条)此前未套 --dsh-composer-* 共享几何(width: calc(100% - 2×side-clearance - 4×dock-inset) + margin: 0 auto + max-width),撑满整列导致与居中输入卡片错位、两条 dock 彼此也未对齐。lib/client.js 的 dockWrap 行内样式与 .prep-dock CSS 补上同一套居中限宽几何(复用 var(--dsh-composer-side-clearance) / var(--dsh-composer-dock-inset) / var(--dsh-composer-card-max-width)),与官方 todo/goal/queue dock 卡片同宽居中;client 刷新页面即生效。

  • 0.5.1 归档语义修正(修复「进 Play 域重载被 autoUnbindOnArchive 解绑」):归档的定义改为「本 DSH 会话被归档(session/disposed)」→ 自动解绑该会话锁定的实例,而不是「实例被归档」。实例掉线/域重载对已绑定会话是无感的——绑定保持、不做 probe 主动解绑、也不注入通知(移除 notifyUnbindOnArchive / unbindOfflineStreak / unbindArchiveGraceMs)。真正的实例离线只在调用时处理:unity_mcp/umcp_* 路由到实例时给足超时(新增 callReconnectTimeoutMs,默认 20s)让官方 mcp-for-unity 服务端的会话重连等待(默认 20s)完成,超过才判定实例离线并返回失败原因(不再抛出)。订阅 session/disposed 作为唯一自动解绑入口(autoUnbindOnArchive 默认开启)。smoke 重构归档场景,181 项全过。

  • 0.1.0 v1:会话→服务绑定 + 探活 + 面板;

  • 0.2.0 v2:实例级——实例发现、会话目标实例锁定、unity_mcp 代理(per MCP-Session-Id 隔离)、unity_pool_scan;

  • 0.3.0 v3:客户端全行内样式(不再注入全局 <style>,避免影响其它客户端插件样式;弹窗改为贴近按钮、无遮罩、toggle 开关)。

  • 0.3.1 首次绑定返回工具列表:unity_pool_bind 改为 async,会话首次绑定时(此前未锁定过)自动拉取目标服务上的 MCP 工具列表(tools/list),随结果返回 tools(含 name/description/inputSchema)与 toolsCount;拉取失败不阻断绑定(附 toolsError)。

  • 0.3.2 动态工具集合对齐 + 内容占位:① 轻量一致性——unity_mcp 请求的工具不在缓存列表时自动重拉一次 tools/list 再转发(官方工具集合动态增减:Unity 自定义工具注册/manage_tools 组开关),重拉失败不阻断;② image/audio/resource 内容块不再静默丢弃,text 输出以 [image: image/png, 内容已丢弃(文本通道)] 占位(与官方 dsh-mcp-client 桥行为一致,对应 manage_camera include_image=true 场景)。

  • 0.3.3 跨服务切换重拉工具列表 + 描述同步:unity_pool_bind 在会话切换到另一服务(serviceId 变化)时同样重拉 tools/list 随结果返回(不同 mcp-for-unity server 工具集可能不同);工具描述、系统提示指南、README(工作流/工具表/HTTP API)同步说明首次绑定/跨服务返回 tools、动态重拉、图片占位。

  • 0.3.4 服务级并集说明:工具列表为服务级并集(同服务多工程实例的自定义工具合并列出)——工具描述、系统提示指南、README 补充说明,避免把其他工程实例的自定义工具误当作当前实例可用(导包迁移任务双工程同服务场景实测)。

  • 0.3.7 忙时等待 + 失败附状态:① unity_mcp 转发前用 execute_code 探测 Unity 编辑器忙状态(isCompiling/isUpdating/Progress),忙则按 busyWaitIntervalMs 间隔重试,总时长不超过 busyMaxWaitMs(默认 10s);探测失败视为"可能忙"(域重载窗口 execute_code 可能不可用)保守等待后继续;② 调用最终失败(isError)时把最近一次探测状态附到返回 editorState;关闭 busyWaitEnabled 可跳过忙时等待(仅失败时补一次探测附状态)。真实服务联调验证通过(失败返回带 editorState: isCompiling=0,isUpdating=0,progressCount=0)。

  • 0.3.8 归档自动解绑:每次探活(probe)完成后检查会话绑定——绑定实例不在最新发现列表(instance-archived)、服务连续离线达阈值(service-offline,unbindOfflineStreak 默认 2 防瞬时抖动)、服务配置不存在(service-removed)时自动解绑该会话(删除绑定 + 关闭 MCP 会话 + 持久化),避免会话停留在已归档实例上;实例发现失败(instancesValid=false)保留上次列表不清空、不据此判归档(发现失败≠实例消失);新增配置 autoUnbindOnArchive(默认 true)/ unbindOfflineStreak(默认 2),view 暴露 instancesValid/offlineStreak/lastAutoUnbind,HTTP /api/config 同步返回;测试扩到 76 项。

  • 0.3.9 归档解绑动态通知:注册 systemPrompt.context('unity-pool:archive')(text 为函数,每次 agent request 前求值——官方机制,sandbox-policy 同款);自动解绑后只向被解绑的会话注入中文通知(时间/实例/原因 + 重新 bind 指引),其他会话注入空串,下一轮 request 自动感知,无需碰 unity_mcp 才撞上「未锁定」报错;新增配置 notifyUnbindOnArchive(默认 true),view.rules / HTTP /api/config 同步返回;测试扩到 82 项。

  • 0.4.0 状态携带(每次发出指令携带 Unity 状态):总开关 stateEnabled + 7 项子开关(stateGameScreenshot/stateSceneScreenshot/stateSelection/stateUiSnapshot/stateSerialized/stateConsoleAll/stateConsoleSelected),默认全关;后台采集器(stateRefreshMs 周期)对已绑定会话采集开启项到缓存——game/scene 视图截图(manage_camera include_image,base64 解码 PNG 落盘 stateDir/<sessionId>/,上下文给文件路径),当前选中项(execute_code 读 UnityEditor.Selection),选中物体 ui-snapshot(ui_snapshot 工具)与序列化字段(mcpforunity://.../components 资源,均防超长截断 stateMaxChars),Console 全文(read_console)与 Console 选中条目(反射 ConsoleWindow.m_ActiveText);注册 systemPrompt.context('unity-pool:state') 同步注入每轮 request;新增 unity_pool_state 工具 + HTTP /api/state、/api/state-refresh、/api/state-switch;view.state / HTTP /api/config 同步返回;测试扩到 117 项。

  • 0.4.0(UX 补充) 开关贴近输入框:新增客户端 conversation.input.dock 槽注册(输入框正上方一条:总开关 + 7 项子开关,紧凑短标签 Game/Scene/选中/快照/序列化/Console全/Console选,采集摘要移入悬浮提示,防溢出会话内容区),未绑定实例自动灰态;会话头部「Unity」胶囊面板内的开关区保留(两处同源,任意切换即时生效,页面刷新即可加载,无需重启 web)。

  • 0.4.0(注入治理) 状态只在用户发消息时携带一次:unity-pool:state context 改为每回合只注入一次——按会话回合号去重(stateTurnOf 从 agent.session.events 读最近 turn/start,agent-loop 每轮用户输入回合号 +1):回合首个 request 注入最新快照,同回合后续工具循环 request 不再重复注入(避免一轮会话内同一状态块被反复塞进上下文 5~6 次);下次发消息(回合号 +1)自动携带最新快照;无回合号(无法去重)保持原行为。客户端配套:轮询失败/非 ok 保留上次开关状态(瞬时网络抖动不再把开关“关掉”);头部「Unity」弹窗打开时强制重拉(与输入条开关实时同步,不再显示旧快照)。测试扩到 126 项全过。

  • 0.4.1 工具列表随时可得 + 未知工具错误自描述 + 客户端两处开关同源(源于「重命名健康度构成面板多文本组件」会话复盘:二次绑定拿不到列表 → Agent 盲猜 read_editor_state → Unknown tool 空转;用户实测头部面板与输入条 dock 两处开关不同步):① unity_pool_bind 每次绑定都返回该服务最新 tools(同服务重复绑定也重拉 tools/list 保持动态工具集合新鲜;拉取失败回退上次缓存并附 toolsError);② unity_pool_status/view 新增工具名速查 tools: {serviceId, count, names}(未绑定为 null);③ unity_mcp 收到 Unknown tool 错误时自动附带当前可用工具名列表 + 相似工具名提示(Levenshtein)+ unity_pool_state 指引;④ 系统提示指南/工具描述/README 同步(含「查编辑器状态用 unity_pool_state,不要猜 MCP 工具名」);⑤ 客户端共享状态源:头部「Unity」面板与输入条 dock 不再各自 useState + 各自轮询(面板不轮询、dock 5s 轮询 → 一边切换另一边最多延迟 5s、面板要重开才刷新),改为模块级 pub-sub store(按 sessionId 分槽,两处组件订阅同源)——任何一处切换成功立即写 store 广播、两处即时同步,5s 轮询仅作外部变化兜底,store 级 switchSeq 过期保护(仅用户切换递增;读取刷新互不干扰——避免「两组件轮询竞争使 dock 首次加载永远失败、开关条消失」的实测事故)防止在途旧快照覆盖新切换;⑥ 胶囊绑定态自动同步:store 扩展存精简绑定信息({serviceId, instanceId, instanceName, serviceName, alive}),胶囊 chip 的名字/颜色优先从 store 读;胶囊增加 5s 轻轮询(带 seq 过期保护)——agent 工具调用 bind/unbind、其它标签页的变化 ≤5s 自动反映,面板内点锁定/解绑即时更新;⑦ 面板锁定并行化:「锁定」按钮普通绑定失败且错误含「已被会话锁定」时自动以 force=true 重试一次(并行开发是插件设计常态),成功即锁定、失败才提示「强制锁定失败」;⑧ 旧轮询快照不覆盖新切换(修复「开关过一会自己关掉」):refresh/post 响应里的 state 写入 store 前检查 switchSeq(请求发出期间若有用户切换则旧快照只更新 binding、不覆盖 state;storeSet 的 state/binding 参数支持 undefined=保留)。测试:host smoke 131 项全过 + client VM 冒烟扩到 30 项全过(scripts/client-vm-smoke.mjs:dock 切总开关 → 面板即时变、面板切子开关 → dock 芯片即时变、外部解绑 → 胶囊自动变回 Unity、外部绑定 → 胶囊自动恢复实例名、dock「未绑定 Unity」文本同步、面板点锁定/解绑 → 胶囊即时变、并行锁定(被占用实例自动 force 重试)、旧轮询快照晚到不覆盖新切换、sessionId 隔离)。

  • 0.4.2 状态开关按会话独立 + 未绑定可切(用户指出「开关全会话共享,却只有绑定会话能控制 → 不成交互闭环」):① 开关 per-session:stateSwitchesBySession 按 sessionId 持久化({ [sessionId]: {key: bool} },含"关"),生效值 = 配置默认 ← 旧全局层 ← 本会话覆盖;stateCarryEnabled/collectState/stateContextText/注入/view.state 全部按会话读开关——每个会话的「控制 → 采集 → 注入 → 感知」全链路闭环,互不干扰;/api/state-switch 的 sessionId 改为必填(缺省 400);/api/config 返回全局默认层(配置 + 旧全局 base);② 旧版全局平铺 stateSwitches 自动迁移为全局默认层(base),未切换过的会话继承它,升级不丢设置;③ 未绑定 Unity 可切开关(预配置):dock 总开关/子项不再 disabled:!bound,未绑定灰显提示「未绑定 Unity,绑定后生效」,绑定后立即生效;子开关仍受总开关 gate(总关时禁用);头部面板本来就可切,补文案「开关按会话记忆」;④ 诊断 switchLog 记录 sessionId。测试:host smoke 扩到 166 项全过 + client VM 冒烟扩到 31 项全过(含 per-session 隔离、未绑定可切断言)。

  • 0.5.0 原生工具注册(Claude 式工具清单)(源于「将车身总拼岛迁移A岛界面任务」会话复盘:code 预设下模型只能经 run_code 调 tools.*,绑定脚本打印子集且字段取错 → 上下文没有工具清单 → 盲猜 find_objects → Unknown tool 空转;Claude 侧是 MCP tools/list 全量原生注入,天然有清单):① 绑定成功后把该服务的 MCP 工具动态注册为 DSH 原生工具 umcp_<工具名>——模型上下文直接可见全部工具名/描述/参数并原生调用(execute 统一转发 proxyMcp,未绑定报错/实例激活/忙等待/未知工具提示全复用);code 预设的 tools:sdk 声明 / native 预设的工具 schema 都会列出;② 配置 nativeToolsEnabled(默认开)/ nativeToolSchema(compact 默认:基础标量类型 + description、复杂/嵌套参数不展开省上下文;full:完整 inputSchema 逐级映射,与 Claude 注册 MCP 工具一致);③ 注册键=服务(syncNativeTools 先注销旧注册再全量重注册),跨服务同名工具后绑定者接管,解绑收敛(服务不再被任何会话绑定时注销该服务 umcp_*,手动解绑与归档自动解绑共用;仍有会话绑定则保留),stop() 全量注销(tools.register 返回的 disposer);④ 工具集合动态变化(Unity 注册/注销、manage_tools 开关)在 bind / unity_mcp 重拉 tools/list 时同步刷新;⑤ view.nativeTools 按服务返回已注册摘要(count/names),view.rules 暴露开关与形态。边界与成本(实测):compact 57 工具 ≈ 14K tokens/轮、full ≈ 20K tokens/轮(固定输入,DeepSeek 缓存友好);工具数受「跨服务同名接管」限制为并集(不会 57×服务数);同服务多会话并发绑定/解绑安全(注册同步幂等、路由按会话绑定不交叉);未绑定会话可见 umcp_* 但调用报「未锁定」;首次绑定 tools/list 拉取失败时本次不注册(回退缓存,下次成功绑定/重拉自动注册)。测试:host smoke 扩到 190 项全过(+18:绑定注册/compact 标量保留+复杂不展开/full 完整 schema+顶层 required/未绑定报错/已绑定转发/HTTP nativeTools 摘要/stop 注销/跨服务同名接管/解绑收敛×3/同服务两会话并发绑定解绑/禁用不注册)。

About

DSH plugin: per-session Unity service pool + instance-level MCP proxy (unity_mcp)

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages