unity-open-mcp 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +33 -2
  2. package/dist/agent-identity.d.ts +12 -0
  3. package/dist/agent-identity.js +2 -0
  4. package/dist/agent-identity.js.map +1 -1
  5. package/dist/batch-spawn.d.ts +39 -0
  6. package/dist/batch-spawn.js +287 -69
  7. package/dist/batch-spawn.js.map +1 -1
  8. package/dist/capabilities/build-capabilities.js +2 -0
  9. package/dist/capabilities/build-capabilities.js.map +1 -1
  10. package/dist/capabilities/discovery.d.ts +51 -0
  11. package/dist/capabilities/discovery.js +87 -0
  12. package/dist/capabilities/discovery.js.map +1 -0
  13. package/dist/capabilities/lifecycle.d.ts +6 -0
  14. package/dist/capabilities/lifecycle.js +17 -1
  15. package/dist/capabilities/lifecycle.js.map +1 -1
  16. package/dist/capabilities/tool-groups.js +1 -0
  17. package/dist/capabilities/tool-groups.js.map +1 -1
  18. package/dist/child-supervision.d.ts +6 -0
  19. package/dist/child-supervision.js +12 -0
  20. package/dist/child-supervision.js.map +1 -1
  21. package/dist/cli/args.d.ts +24 -2
  22. package/dist/cli/args.js +97 -2
  23. package/dist/cli/args.js.map +1 -1
  24. package/dist/cli/cli.js +42 -3
  25. package/dist/cli/cli.js.map +1 -1
  26. package/dist/cli/help-text.d.ts +1 -0
  27. package/dist/cli/help-text.js +71 -3
  28. package/dist/cli/help-text.js.map +1 -1
  29. package/dist/cli/json-guards.d.ts +2 -0
  30. package/dist/cli/json-guards.js +5 -0
  31. package/dist/cli/json-guards.js.map +1 -0
  32. package/dist/cli/setup-command.d.ts +88 -0
  33. package/dist/cli/setup-command.js +444 -0
  34. package/dist/cli/setup-command.js.map +1 -0
  35. package/dist/cli/update-command.d.ts +30 -0
  36. package/dist/cli/update-command.js +271 -0
  37. package/dist/cli/update-command.js.map +1 -0
  38. package/dist/compile-verify.d.ts +3 -0
  39. package/dist/compile-verify.js +4 -0
  40. package/dist/compile-verify.js.map +1 -1
  41. package/dist/constants.d.ts +10 -2
  42. package/dist/constants.js +10 -2
  43. package/dist/constants.js.map +1 -1
  44. package/dist/dialog-dismiss.d.ts +108 -12
  45. package/dist/dialog-dismiss.js +294 -109
  46. package/dist/dialog-dismiss.js.map +1 -1
  47. package/dist/dialog-policy.d.ts +55 -9
  48. package/dist/dialog-policy.js +85 -15
  49. package/dist/dialog-policy.js.map +1 -1
  50. package/dist/editor-search.d.ts +2 -0
  51. package/dist/editor-search.js +8 -0
  52. package/dist/editor-search.js.map +1 -0
  53. package/dist/gate-error.d.ts +3 -0
  54. package/dist/gate-error.js.map +1 -1
  55. package/dist/index.js +55 -1
  56. package/dist/index.js.map +1 -1
  57. package/dist/instance-discovery.d.ts +16 -0
  58. package/dist/instance-discovery.js +45 -0
  59. package/dist/instance-discovery.js.map +1 -1
  60. package/dist/jobs/adapters.d.ts +15 -0
  61. package/dist/jobs/adapters.js +147 -0
  62. package/dist/jobs/adapters.js.map +1 -0
  63. package/dist/jobs/handler.d.ts +3 -0
  64. package/dist/jobs/handler.js +29 -0
  65. package/dist/jobs/handler.js.map +1 -0
  66. package/dist/jobs/job-manager.d.ts +123 -0
  67. package/dist/jobs/job-manager.js +290 -0
  68. package/dist/jobs/job-manager.js.map +1 -0
  69. package/dist/live-client.d.ts +50 -2
  70. package/dist/live-client.js +221 -26
  71. package/dist/live-client.js.map +1 -1
  72. package/dist/local-tools.d.ts +17 -0
  73. package/dist/local-tools.js +47 -0
  74. package/dist/local-tools.js.map +1 -0
  75. package/dist/project-path.d.ts +86 -0
  76. package/dist/project-path.js +159 -0
  77. package/dist/project-path.js.map +1 -0
  78. package/dist/release-lookup.d.ts +32 -0
  79. package/dist/release-lookup.js +147 -0
  80. package/dist/release-lookup.js.map +1 -0
  81. package/dist/routers.d.ts +17 -3
  82. package/dist/routers.js +28 -8
  83. package/dist/routers.js.map +1 -1
  84. package/dist/running-unity.d.ts +21 -0
  85. package/dist/running-unity.js +50 -0
  86. package/dist/running-unity.js.map +1 -1
  87. package/dist/schema-defaults.d.ts +4 -3
  88. package/dist/schema-defaults.js +20 -6
  89. package/dist/schema-defaults.js.map +1 -1
  90. package/dist/server-instructions.js +7 -1
  91. package/dist/server-instructions.js.map +1 -1
  92. package/dist/server.d.ts +10 -3
  93. package/dist/server.js +24 -25
  94. package/dist/server.js.map +1 -1
  95. package/dist/setup/portable-config.d.ts +60 -0
  96. package/dist/setup/portable-config.js +249 -0
  97. package/dist/setup/portable-config.js.map +1 -0
  98. package/dist/skill/SKILL.md +68 -0
  99. package/dist/skill/copy-references.d.ts +2 -0
  100. package/dist/skill/copy-references.js +16 -0
  101. package/dist/skill/copy-references.js.map +1 -0
  102. package/dist/skill/generate-skill.js +5 -3
  103. package/dist/skill/generate-skill.js.map +1 -1
  104. package/dist/skill/references/batch-and-gates.md +142 -0
  105. package/dist/skill/references/compile-and-safe-mode.md +163 -0
  106. package/dist/skill/references/discovery-and-groups.md +30 -0
  107. package/dist/skill/references/routing-and-lifecycle.md +62 -0
  108. package/dist/skill/references/senses-and-tests.md +41 -0
  109. package/dist/skill/references/yaml-and-offline-work.md +33 -0
  110. package/dist/templates/mcp-wrapper.sh +35 -0
  111. package/dist/tool-contract.d.ts +8 -0
  112. package/dist/tool-contract.js +253 -0
  113. package/dist/tool-contract.js.map +1 -0
  114. package/dist/tool-router.d.ts +62 -7
  115. package/dist/tool-router.js +455 -17
  116. package/dist/tool-router.js.map +1 -1
  117. package/dist/tool-session-state.d.ts +6 -0
  118. package/dist/tool-session-state.js +14 -0
  119. package/dist/tool-session-state.js.map +1 -1
  120. package/dist/tools/agent-capabilities.js +9 -0
  121. package/dist/tools/agent-capabilities.js.map +1 -1
  122. package/dist/tools/batch-execute.js +17 -14
  123. package/dist/tools/batch-execute.js.map +1 -1
  124. package/dist/tools/bridge-status.js +4 -5
  125. package/dist/tools/bridge-status.js.map +1 -1
  126. package/dist/tools/compile-check.js +6 -4
  127. package/dist/tools/compile-check.js.map +1 -1
  128. package/dist/tools/component-get.js +2 -2
  129. package/dist/tools/component-get.js.map +1 -1
  130. package/dist/tools/component-modify.js +4 -4
  131. package/dist/tools/component-modify.js.map +1 -1
  132. package/dist/tools/execute-csharp.js +8 -4
  133. package/dist/tools/execute-csharp.js.map +1 -1
  134. package/dist/tools/gameobject-modify.js +2 -1
  135. package/dist/tools/gameobject-modify.js.map +1 -1
  136. package/dist/tools/index.d.ts +2 -0
  137. package/dist/tools/index.js +10 -2
  138. package/dist/tools/index.js.map +1 -1
  139. package/dist/tools/inputsim-key.js +12 -14
  140. package/dist/tools/inputsim-key.js.map +1 -1
  141. package/dist/tools/inputsim-pointer.js +3 -3
  142. package/dist/tools/inputsim-pointer.js.map +1 -1
  143. package/dist/tools/inputsim-step.js +7 -8
  144. package/dist/tools/inputsim-step.js.map +1 -1
  145. package/dist/tools/inputsim-touch.js +9 -10
  146. package/dist/tools/inputsim-touch.js.map +1 -1
  147. package/dist/tools/jobs.d.ts +40 -0
  148. package/dist/tools/jobs.js +18 -0
  149. package/dist/tools/jobs.js.map +1 -0
  150. package/dist/tools/manage-tools.js +10 -5
  151. package/dist/tools/manage-tools.js.map +1 -1
  152. package/dist/tools/project-commands.d.ts +40 -0
  153. package/dist/tools/project-commands.js +23 -0
  154. package/dist/tools/project-commands.js.map +1 -0
  155. package/dist/tools/read-compile-errors.js +14 -93
  156. package/dist/tools/read-compile-errors.js.map +1 -1
  157. package/dist/tools/scan-paths.js +4 -4
  158. package/dist/tools/scan-paths.js.map +1 -1
  159. package/dist/tools/schema-fragments.d.ts +15 -3
  160. package/dist/tools/schema-fragments.js +23 -13
  161. package/dist/tools/schema-fragments.js.map +1 -1
  162. package/dist/tools/upgrade.d.ts +40 -0
  163. package/dist/tools/upgrade.js +55 -0
  164. package/dist/tools/upgrade.js.map +1 -0
  165. package/dist/unity-install-discovery.d.ts +24 -0
  166. package/dist/unity-install-discovery.js +113 -4
  167. package/dist/unity-install-discovery.js.map +1 -1
  168. package/package.json +4 -3
  169. package/templates/mcp-wrapper.sh +35 -0
@@ -0,0 +1,271 @@
1
+ import { execFile } from "node:child_process";
2
+ import { readFile } from "node:fs/promises";
3
+ import { dirname, join, resolve, sep } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { promisify } from "node:util";
6
+ import { isRecord } from "./json-guards.js";
7
+ import { PACKAGE_NAME, compareSemver, resolveLatestVersion, } from "../release-lookup.js";
8
+ // The release lookups live in `release-lookup.ts` (shared with the
9
+ // `unity_open_mcp_upgrade` route); re-exported so this command stays the one
10
+ // entry point existing importers know.
11
+ export { compareSemver, resolveLatestVersion } from "../release-lookup.js";
12
+ const execFileAsync = promisify(execFile);
13
+ export const UPDATE_EXIT = {
14
+ AVAILABLE: 10,
15
+ LOOKUP_FAILED: 11,
16
+ INSTALL_FAILED: 12,
17
+ };
18
+ const defaultDependencies = {
19
+ fetch: globalThis.fetch.bind(globalThis),
20
+ runProcess: async (command, args, cwd) => {
21
+ try {
22
+ const result = await execFileAsync(command, args, {
23
+ cwd,
24
+ encoding: "utf8",
25
+ maxBuffer: 4 * 1024 * 1024,
26
+ // `npm` is `npm.cmd` on Windows, which Node can only start through
27
+ // the shell (a bare execFile fails with `spawn npm ENOENT`). The
28
+ // arguments this command passes are fixed literals, so shell
29
+ // quoting is not a concern.
30
+ shell: process.platform === "win32",
31
+ windowsHide: true,
32
+ });
33
+ return { exitCode: 0, stdout: result.stdout, stderr: result.stderr };
34
+ }
35
+ catch (error) {
36
+ const failed = error;
37
+ return {
38
+ exitCode: typeof failed.code === "number" ? failed.code : 1,
39
+ stdout: failed.stdout ?? "",
40
+ stderr: failed.stderr ?? failed.message,
41
+ };
42
+ }
43
+ },
44
+ env: process.env,
45
+ packageRoot: resolve(dirname(fileURLToPath(import.meta.url)), "../.."),
46
+ cwd: process.cwd(),
47
+ readTextFile: (path) => readFile(path, "utf8"),
48
+ };
49
+ export async function runUpdateCommand(options) {
50
+ const deps = { ...defaultDependencies, ...options.dependencies };
51
+ let latest;
52
+ try {
53
+ latest = await resolveLatestVersion(deps.fetch);
54
+ }
55
+ catch (error) {
56
+ const message = error instanceof Error ? error.message : String(error);
57
+ return {
58
+ exitCode: UPDATE_EXIT.LOOKUP_FAILED,
59
+ json: {
60
+ command: "update",
61
+ check: options.check,
62
+ currentVersion: options.currentVersion,
63
+ status: "lookup_failed",
64
+ error: { code: "update_lookup_failed", message },
65
+ },
66
+ human: `Could not check for updates: ${message}\n` +
67
+ "No files were changed. Retry when npm or GitHub is reachable.",
68
+ errorLabel: "update_lookup_failed",
69
+ };
70
+ }
71
+ const comparison = compareSemver(latest.version, options.currentVersion);
72
+ if (comparison === null) {
73
+ const message = `Cannot compare installed version '${options.currentVersion}' with ` +
74
+ `published version '${latest.version}'.`;
75
+ return {
76
+ exitCode: UPDATE_EXIT.LOOKUP_FAILED,
77
+ json: {
78
+ command: "update",
79
+ check: options.check,
80
+ currentVersion: options.currentVersion,
81
+ latestVersion: latest.version,
82
+ source: latest.source,
83
+ status: "lookup_failed",
84
+ error: { code: "invalid_update_version", message },
85
+ },
86
+ human: `${message}\nNo files were changed.`,
87
+ errorLabel: "invalid_update_version",
88
+ };
89
+ }
90
+ const available = comparison > 0;
91
+ if (options.check) {
92
+ const status = available ? "update_available" : "up_to_date";
93
+ return {
94
+ exitCode: available ? UPDATE_EXIT.AVAILABLE : 0,
95
+ json: {
96
+ command: "update",
97
+ check: options.check,
98
+ currentVersion: options.currentVersion,
99
+ latestVersion: latest.version,
100
+ source: latest.source,
101
+ status,
102
+ updateAvailable: available,
103
+ fallbackReason: latest.fallbackReason,
104
+ },
105
+ human: available
106
+ ? `Update available: ${options.currentVersion} → ${latest.version} (${latest.source}).`
107
+ : `unity-open-mcp ${options.currentVersion} is up to date (${latest.source}).`,
108
+ };
109
+ }
110
+ if (!available) {
111
+ return {
112
+ exitCode: 0,
113
+ json: {
114
+ command: "update",
115
+ check: false,
116
+ currentVersion: options.currentVersion,
117
+ latestVersion: latest.version,
118
+ source: latest.source,
119
+ status: "up_to_date",
120
+ updateAvailable: false,
121
+ fallbackReason: latest.fallbackReason,
122
+ scope: "mcp_server_only",
123
+ },
124
+ human: [
125
+ `unity-open-mcp ${options.currentVersion} is up to date (${latest.source}).`,
126
+ projectUpdateGuidance(),
127
+ ].join("\n"),
128
+ };
129
+ }
130
+ const target = await detectInstallTarget(deps);
131
+ if (target.mode === "npx" || target.mode === "unknown") {
132
+ const invocation = `npx -y ${PACKAGE_NAME}@latest update --check`;
133
+ const reason = target.mode === "npx"
134
+ ? "This process is running from npx's cache, which cannot safely replace itself."
135
+ : "The active package is not an npm global or project-local install.";
136
+ return {
137
+ exitCode: 0,
138
+ json: {
139
+ command: "update",
140
+ check: false,
141
+ currentVersion: options.currentVersion,
142
+ latestVersion: latest.version,
143
+ source: latest.source,
144
+ status: "guidance",
145
+ updateAvailable: true,
146
+ installMode: target.mode,
147
+ applied: false,
148
+ nextCommand: invocation,
149
+ },
150
+ human: [
151
+ `Update available: ${options.currentVersion} → ${latest.version} (${latest.source}).`,
152
+ reason,
153
+ `Change the MCP client pin to ${PACKAGE_NAME}@${latest.version}, or verify the latest release with:`,
154
+ ` ${invocation}`,
155
+ projectUpdateGuidance(),
156
+ ].join("\n"),
157
+ };
158
+ }
159
+ const args = target.mode === "global"
160
+ ? ["install", "--global", `${PACKAGE_NAME}@${latest.version}`]
161
+ : ["install", `${PACKAGE_NAME}@${latest.version}`];
162
+ const install = await deps.runProcess("npm", args, target.cwd);
163
+ if (install.exitCode !== 0) {
164
+ const detail = lastNonEmptyLine(install.stderr) || "npm exited non-zero";
165
+ return {
166
+ exitCode: UPDATE_EXIT.INSTALL_FAILED,
167
+ json: {
168
+ command: "update",
169
+ check: false,
170
+ currentVersion: options.currentVersion,
171
+ latestVersion: latest.version,
172
+ source: latest.source,
173
+ status: "install_failed",
174
+ installMode: target.mode,
175
+ applied: false,
176
+ error: { code: "update_install_failed", message: detail },
177
+ },
178
+ human: `npm could not update ${PACKAGE_NAME} to ${latest.version}: ${detail}\n` +
179
+ "No Unity project or MCP client configuration was changed.",
180
+ errorLabel: "update_install_failed",
181
+ };
182
+ }
183
+ return {
184
+ exitCode: 0,
185
+ json: {
186
+ command: "update",
187
+ check: false,
188
+ currentVersion: options.currentVersion,
189
+ latestVersion: latest.version,
190
+ source: latest.source,
191
+ status: "updated",
192
+ installMode: target.mode,
193
+ applied: true,
194
+ },
195
+ human: [
196
+ `Updated ${PACKAGE_NAME}: ${options.currentVersion} → ${latest.version} (${target.mode} npm install).`,
197
+ "Restart the MCP client so it launches the new server.",
198
+ projectUpdateGuidance(),
199
+ ].join("\n"),
200
+ };
201
+ }
202
+ async function detectInstallTarget(deps) {
203
+ // Comparisons run on a lower-cased copy, but any path handed back to npm
204
+ // (the local install's cwd) must keep the on-disk casing: case-sensitive
205
+ // filesystems reject a lower-cased directory with ENOENT.
206
+ const forwardRoot = forwardSlashes(deps.packageRoot);
207
+ const normalizedRoot = forwardRoot.toLowerCase();
208
+ if (deps.env.npm_command === "exec" ||
209
+ deps.env.npm_lifecycle_event === "npx" ||
210
+ normalizedRoot.includes("/_npx/")) {
211
+ return { mode: "npx" };
212
+ }
213
+ const globalRoot = await deps.runProcess("npm", ["root", "--global"]);
214
+ if (globalRoot.exitCode === 0) {
215
+ const normalizedGlobal = normalizePath(globalRoot.stdout.trim());
216
+ if (normalizedGlobal && isWithin(normalizedRoot, normalizedGlobal)) {
217
+ return { mode: "global" };
218
+ }
219
+ }
220
+ // A project-local install is only certain when the owning package.json
221
+ // declares this package. Without that check a global install whose
222
+ // `npm root -g` probe failed would be mistaken for a local one and npm
223
+ // would be run inside the global lib directory.
224
+ const markerIndex = forwardRoot.search(/\/node_modules\//i);
225
+ if (markerIndex > 0) {
226
+ const owner = denormalizePath(forwardRoot.slice(0, markerIndex));
227
+ if (await declaresDependency(deps, owner)) {
228
+ return { mode: "local", cwd: owner };
229
+ }
230
+ }
231
+ return { mode: "unknown", cwd: deps.cwd };
232
+ }
233
+ async function declaresDependency(deps, projectDir) {
234
+ try {
235
+ const manifest = JSON.parse(await deps.readTextFile(join(projectDir, "package.json")));
236
+ if (!isRecord(manifest))
237
+ return false;
238
+ return ["dependencies", "devDependencies", "optionalDependencies"].some((field) => {
239
+ const section = manifest[field];
240
+ return isRecord(section) && typeof section[PACKAGE_NAME] === "string";
241
+ });
242
+ }
243
+ catch {
244
+ return false;
245
+ }
246
+ }
247
+ function projectUpdateGuidance() {
248
+ return ("This command updates only the MCP server. Next, update both Unity packages and client pins " +
249
+ "from the bridge window's Updates section (or use the setup wizard/manual package path), " +
250
+ "then run `unity-open-mcp ping` or `unity-open-mcp status` to confirm versions.");
251
+ }
252
+ function forwardSlashes(path) {
253
+ return path.replaceAll("\\", "/").replace(/\/$/, "");
254
+ }
255
+ function normalizePath(path) {
256
+ return forwardSlashes(path).toLowerCase();
257
+ }
258
+ function denormalizePath(path) {
259
+ return sep === "\\" ? path.replaceAll("/", "\\") : path;
260
+ }
261
+ function isWithin(candidate, parent) {
262
+ return candidate === parent || candidate.startsWith(`${parent}/`);
263
+ }
264
+ function lastNonEmptyLine(value) {
265
+ return (value
266
+ .split(/\r?\n/)
267
+ .map((line) => line.trim())
268
+ .filter(Boolean)
269
+ .at(-1) ?? "");
270
+ }
271
+ //# sourceMappingURL=update-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-command.js","sourceRoot":"","sources":["../../src/cli/update-command.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,9 @@
8
8
  export interface CompileVerifySnapshot {
9
9
  /** Count of tools the bridge compiled in, or undefined when unreachable. */
10
10
  bridgeToolCount?: number;
11
+ compileGeneration?: number;
12
+ compileStatus?: string;
13
+ sourceMatches?: boolean;
11
14
  /**
12
15
  * Newest mtime (epoch ms) of Library/ScriptAssemblies/*.dll, or undefined.
13
16
  * Captured from the MCP server side via fs.statSync.
@@ -55,6 +55,10 @@ export const COMPILE_VERIFY_RECOMMENDATIONS = {
55
55
  */
56
56
  export function detectCompileVerify(input) {
57
57
  const { before, after, sourceMtimeMs } = input;
58
+ // A completed generation with identical input content can legitimately retain DLL mtimes.
59
+ if (after.sourceMatches === true || after.compileStatus === "currently_compiling") {
60
+ return { code: null, recommendation: null };
61
+ }
58
62
  // 1. dll_stale — highest confidence: the DLL is provably older than the edit.
59
63
  if (sourceMtimeMs !== undefined &&
60
64
  after.dllMtimeMs !== undefined &&
@@ -1 +1 @@
1
- {"version":3,"file":"compile-verify.js","sourceRoot":"","sources":["../src/compile-verify.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,6EAA6E;AAC7E,0EAA0E;AAC1E,+EAA+E;AAC/E,yEAAyE;AACzE,uEAAuE;AACvE,uBAAuB;AACvB,EAAE;AACF,wEAAwE;AACxE,8EAA8E;AAC9E,0EAA0E;AAC1E,oEAAoE;AACpE,+DAA+D;AAC/D,+EAA+E;AAC/E,wDAAwD;AACxD,EAAE;AACF,wDAAwD;AACxD,2DAA2D;AAC3D,qEAAqE;AACrE,mEAAmE;AAwCnE;;;GAGG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAgD;IACzF,YAAY,EACV,kEAAkE;QAClE,mEAAmE;QACnE,oEAAoE;QACpE,gEAAgE;QAChE,oEAAoE;QACpE,0CAA0C;IAC5C,SAAS,EACP,qEAAqE;QACrE,mEAAmE;QACnE,qEAAqE;QACrE,oEAAoE;QACpE,mEAAmE;QACnE,qBAAqB;CACxB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAyB;IAC3D,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,KAAK,CAAC;IAE/C,8EAA8E;IAC9E,IACE,aAAa,KAAK,SAAS;QAC3B,KAAK,CAAC,UAAU,KAAK,SAAS;QAC9B,KAAK,CAAC,UAAU,GAAG,aAAa,EAChC,CAAC;QACD,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,cAAc,EAAE,8BAA8B,CAAC,SAAS;SACzD,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,6EAA6E;IAC7E,uCAAuC;IACvC,MAAM,cAAc,GAClB,MAAM,CAAC,eAAe,KAAK,SAAS;QACpC,KAAK,CAAC,eAAe,KAAK,SAAS;QACnC,MAAM,CAAC,eAAe,KAAK,KAAK,CAAC,eAAe,CAAC;IACnD,MAAM,gBAAgB,GACpB,MAAM,CAAC,UAAU,KAAK,SAAS;QAC/B,KAAK,CAAC,UAAU,KAAK,SAAS;QAC9B,KAAK,CAAC,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC;IAExC,IAAI,cAAc,IAAI,gBAAgB,EAAE,CAAC;QACvC,OAAO;YACL,IAAI,EAAE,cAAc;YACpB,cAAc,EAAE,8BAA8B,CAAC,YAAY;SAC5D,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAC1C,MAA2B;IAE3B,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,IAAI,MAAM,CAAC,cAAc,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACxE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC;AACtE,CAAC"}
1
+ {"version":3,"file":"compile-verify.js","sourceRoot":"","sources":["../src/compile-verify.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,6EAA6E;AAC7E,0EAA0E;AAC1E,+EAA+E;AAC/E,yEAAyE;AACzE,uEAAuE;AACvE,uBAAuB;AACvB,EAAE;AACF,wEAAwE;AACxE,8EAA8E;AAC9E,0EAA0E;AAC1E,oEAAoE;AACpE,+DAA+D;AAC/D,+EAA+E;AAC/E,wDAAwD;AACxD,EAAE;AACF,wDAAwD;AACxD,2DAA2D;AAC3D,qEAAqE;AACrE,mEAAmE;AA2CnE;;;GAGG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAgD;IACzF,YAAY,EACV,kEAAkE;QAClE,mEAAmE;QACnE,oEAAoE;QACpE,gEAAgE;QAChE,oEAAoE;QACpE,0CAA0C;IAC5C,SAAS,EACP,qEAAqE;QACrE,mEAAmE;QACnE,qEAAqE;QACrE,oEAAoE;QACpE,mEAAmE;QACnE,qBAAqB;CACxB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAyB;IAC3D,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,KAAK,CAAC;IAE/C,0FAA0F;IAC1F,IAAI,KAAK,CAAC,aAAa,KAAK,IAAI,IAAI,KAAK,CAAC,aAAa,KAAK,qBAAqB,EAAE,CAAC;QAClF,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;IAC9C,CAAC;IAED,8EAA8E;IAC9E,IACE,aAAa,KAAK,SAAS;QAC3B,KAAK,CAAC,UAAU,KAAK,SAAS;QAC9B,KAAK,CAAC,UAAU,GAAG,aAAa,EAChC,CAAC;QACD,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,cAAc,EAAE,8BAA8B,CAAC,SAAS;SACzD,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,6EAA6E;IAC7E,uCAAuC;IACvC,MAAM,cAAc,GAClB,MAAM,CAAC,eAAe,KAAK,SAAS;QACpC,KAAK,CAAC,eAAe,KAAK,SAAS;QACnC,MAAM,CAAC,eAAe,KAAK,KAAK,CAAC,eAAe,CAAC;IACnD,MAAM,gBAAgB,GACpB,MAAM,CAAC,UAAU,KAAK,SAAS;QAC/B,KAAK,CAAC,UAAU,KAAK,SAAS;QAC9B,KAAK,CAAC,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC;IAExC,IAAI,cAAc,IAAI,gBAAgB,EAAE,CAAC;QACvC,OAAO;YACL,IAAI,EAAE,cAAc;YACpB,cAAc,EAAE,8BAA8B,CAAC,YAAY;SAC5D,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAC1C,MAA2B;IAE3B,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,IAAI,MAAM,CAAC,cAAc,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACxE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC;AACtE,CAAC"}
@@ -78,7 +78,15 @@ export declare const BRIDGE_HOST_SAFE_TIMEOUT_CAP_MS = 55000;
78
78
  * (`packages/bridge/Editor/Bridge/BridgeSession.cs`), which carries the
79
79
  * revision log.
80
80
  */
81
- export declare const EXPECTED_BRIDGE_WIRE_CONTRACT = 1;
81
+ export declare const EXPECTED_BRIDGE_WIRE_CONTRACT = 2;
82
+ /**
83
+ * Request/response header pair for the compile state that qualifies a tool
84
+ * result (`BridgeCompileState.Header`). Sent as `1` with a tool call, it asks
85
+ * the bridge to snapshot the state inside that call's own main-thread dispatch
86
+ * and return it (base64 UTF-8 JSON) under the same name; an older bridge
87
+ * ignores it, and the server then reads `/compile-state`.
88
+ */
89
+ export declare const COMPILE_STATE_HEADER = "X-Unity-Open-MCP-Compile-State";
82
90
  /**
83
91
  * The npm package the MCP server is published as, pinned to the shared
84
92
  * trio version (`unity-open-mcp@X.Y.Z`) for the `npx -y` invocation.
@@ -86,7 +94,7 @@ export declare const EXPECTED_BRIDGE_WIRE_CONTRACT = 1;
86
94
  * verify packages. Mirrors the C# `BridgeConstants.NpmPackage`. Kept in
87
95
  * sync by `scripts/sync-version.mjs`.
88
96
  */
89
- export declare const NPM_PACKAGE = "unity-open-mcp@1.2.3";
97
+ export declare const NPM_PACKAGE = "unity-open-mcp@1.3.0";
90
98
  /**
91
99
  * Build the bridge base URL for a given port. Centralizes the loopback +
92
100
  * scheme so every caller produces the same string.
package/dist/constants.js CHANGED
@@ -89,7 +89,15 @@ export const BRIDGE_HOST_SAFE_TIMEOUT_CAP_MS = 55_000;
89
89
  * (`packages/bridge/Editor/Bridge/BridgeSession.cs`), which carries the
90
90
  * revision log.
91
91
  */
92
- export const EXPECTED_BRIDGE_WIRE_CONTRACT = 1;
92
+ export const EXPECTED_BRIDGE_WIRE_CONTRACT = 2;
93
+ /**
94
+ * Request/response header pair for the compile state that qualifies a tool
95
+ * result (`BridgeCompileState.Header`). Sent as `1` with a tool call, it asks
96
+ * the bridge to snapshot the state inside that call's own main-thread dispatch
97
+ * and return it (base64 UTF-8 JSON) under the same name; an older bridge
98
+ * ignores it, and the server then reads `/compile-state`.
99
+ */
100
+ export const COMPILE_STATE_HEADER = "X-Unity-Open-MCP-Compile-State";
93
101
  /**
94
102
  * The npm package the MCP server is published as, pinned to the shared
95
103
  * trio version (`unity-open-mcp@X.Y.Z`) for the `npx -y` invocation.
@@ -97,7 +105,7 @@ export const EXPECTED_BRIDGE_WIRE_CONTRACT = 1;
97
105
  * verify packages. Mirrors the C# `BridgeConstants.NpmPackage`. Kept in
98
106
  * sync by `scripts/sync-version.mjs`.
99
107
  */
100
- export const NPM_PACKAGE = "unity-open-mcp@1.2.3";
108
+ export const NPM_PACKAGE = "unity-open-mcp@1.3.0";
101
109
  /**
102
110
  * Build the bridge base URL for a given port. Centralizes the loopback +
103
111
  * scheme so every caller produces the same string.
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,6EAA6E;AAC7E,2EAA2E;AAC3E,yEAAyE;AACzE,yEAAyE;AACzE,gCAAgC;AAChC,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,4EAA4E;AAC5E,SAAS;AAET;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEjD;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,4BAA4B,CAAC;AAEzD;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,WAAW,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,wCAAwC,CAAC;AAC1E,MAAM,CAAC,MAAM,eAAe,GAAG,sCAAsC,CAAC;AAEtE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC;AAC3C,MAAM,CAAC,MAAM,qBAAqB,GAAG,OAAO,CAAC;AAE7C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,MAAM,CAAC;AAEtD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC;AAE/C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,sBAAsB,CAAC;AAElD;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,UAAU,aAAa,IAAI,IAAI,EAAE,CAAC;AAC3C,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,QAAQ;CACT,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,2CAA2C,CAAC;AAEvE;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GACnC,8CAA8C,CAAC;AAEjD;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,+CAA+C,CAAC"}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,6EAA6E;AAC7E,2EAA2E;AAC3E,yEAAyE;AACzE,yEAAyE;AACzE,gCAAgC;AAChC,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,4EAA4E;AAC5E,SAAS;AAET;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEjD;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,4BAA4B,CAAC;AAEzD;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,WAAW,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,wCAAwC,CAAC;AAC1E,MAAM,CAAC,MAAM,eAAe,GAAG,sCAAsC,CAAC;AAEtE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC;AAC3C,MAAM,CAAC,MAAM,qBAAqB,GAAG,OAAO,CAAC;AAE7C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,MAAM,CAAC;AAEtD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC;AAE/C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,gCAAgC,CAAC;AAErE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,sBAAsB,CAAC;AAElD;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,UAAU,aAAa,IAAI,IAAI,EAAE,CAAC;AAC3C,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,QAAQ;CACT,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,2CAA2C,CAAC;AAEvE;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GACnC,8CAA8C,CAAC;AAEjD;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,+CAA+C,CAAC"}
@@ -72,6 +72,8 @@ export interface DismissProbeOptions {
72
72
  * the modal is blocked (audit line, no click) unless this is set.
73
73
  */
74
74
  allowUnsavedSceneDismiss: boolean;
75
+ /** Allow continuing through a non-matching Editor warning. Off by default. */
76
+ allowVersionMismatch: boolean;
75
77
  /**
76
78
  * M12 — optional abort signal. When fired while a probe is in flight, the
77
79
  * underlying `execFile`/`execFileSync` child is killed so the loop can exit
@@ -108,8 +110,9 @@ export declare function tryDismissDialog(opts: DismissProbeOptions): Promise<Dis
108
110
  * Strategy:
109
111
  * 1. P/Invoke `EnumWindows` to enumerate every visible top-level window
110
112
  * owned by `Unity.exe`.
111
- * 2. For each, normalize the title (alphanumeric lowercase) and match it
112
- * against the embedded per-kind fragment table to classify it.
113
+ * 2. For each, skip the main Editor window ({@link isUnityEditorWindowTitle}),
114
+ * normalize the title (alphanumeric lowercase) and match it against the
115
+ * embedded per-kind fragment table to classify it.
113
116
  * 3. If the kind is in the blocked list → emit `blocked:<kind>` and skip
114
117
  * the click. If the kind's token list is null → skip (not-found).
115
118
  * 4. Walk child windows with `EnumChildWindows`, find a Button whose
@@ -127,25 +130,94 @@ export declare function tryDismissDialog(opts: DismissProbeOptions): Promise<Dis
127
130
  * The token table is passed via stdin (one JSON blob) so the script body
128
131
  * stays constant and testable; embedding it inline would bloat the assertion.
129
132
  */
130
- export declare const WINDOWS_DISMISS_PS_SCRIPT = "\n$ErrorActionPreference = 'Stop'\ntry {\n if (-not ([System.Management.Automation.PSTypeName]'UnityOpenMcp.Dialogs.Dismisser').Type) {\n Add-Type -TypeDefinition @\"\nusing System;\nusing System.Collections.Generic;\nusing System.Runtime.InteropServices;\nusing System.Text;\nnamespace UnityOpenMcp.Dialogs {\n public static class Dismisser {\n public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);\n [DllImport(\"user32.dll\")] static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);\n [DllImport(\"user32.dll\", CharSet=CharSet.Unicode)] static extern int GetWindowTextW(IntPtr hWnd, StringBuilder lpString, int nMaxCount);\n [DllImport(\"user32.dll\", CharSet=CharSet.Unicode)] static extern int GetClassNameW(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);\n [DllImport(\"user32.dll\")] static extern bool IsWindowVisible(IntPtr hWnd);\n [DllImport(\"user32.dll\")] static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);\n [DllImport(\"user32.dll\")] static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);\n [DllImport(\"user32.dll\", CharSet=CharSet.Unicode)] static extern IntPtr SendMessageW(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);\n const uint BM_CLICK = 0x00F5;\n static string Norm(string s) {\n var sb = new StringBuilder(s.Length);\n foreach (var ch in s) {\n if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {\n sb.Append(char.ToLowerInvariant(ch));\n }\n }\n return sb.ToString();\n }\n public static string TryDismiss(int[] unityPids, string tableJson) {\n var unityPidSet = new HashSet<uint>();\n for (int i = 0; i < unityPids.Length; i++) unityPidSet.Add((uint)unityPids[i]);\n var table = System.Text.Json.JsonSerializer.Deserialize<Types.Table>(tableJson);\n var candidates = new List<IntPtr>();\n var sb = new StringBuilder(512);\n EnumWindows((hWnd, lParam) => {\n if (!IsWindowVisible(hWnd)) return true;\n uint procId; GetWindowThreadProcessId(hWnd, out procId);\n if (!unityPidSet.Contains(procId)) return true;\n sb.Length = 0;\n GetWindowTextW(hWnd, sb, sb.Capacity);\n candidates.Add(hWnd);\n return true;\n }, IntPtr.Zero);\n foreach (var hWnd in candidates) {\n sb.Length = 0;\n GetWindowTextW(hWnd, sb, sb.Capacity);\n var title = sb.ToString();\n var norm = Norm(title);\n string kind = null;\n foreach (var kv in table.kinds) {\n foreach (var frag in kv.Value.fragments) {\n if (norm.Contains(frag)) { kind = kv.Key; break; }\n }\n if (kind != null) break;\n }\n string[] tokens = null;\n if (kind != null) {\n if (table.blocked.Contains(kind)) return \"blocked:\" + kind;\n if (!table.kinds[kind].tokensSpecified) continue;\n tokens = table.kinds[kind].tokens;\n } else {\n if (table.genericTokens.Length == 0) continue;\n tokens = table.genericTokens;\n }\n IntPtr matchedButton = IntPtr.Zero;\n string matchedText = null;\n EnumChildWindows(hWnd, (hChild, lParam) => {\n sb.Length = 0;\n GetClassNameW(hChild, sb, sb.Capacity);\n if (sb.ToString() != \"Button\") return true;\n sb.Length = 0;\n GetWindowTextW(hChild, sb, sb.Capacity);\n var text = sb.ToString();\n var textNorm = Norm(text);\n foreach (var token in tokens) {\n if (textNorm.Contains(token)) { matchedButton = hChild; matchedText = text; return false; }\n }\n return true;\n }, IntPtr.Zero);\n if (matchedButton == IntPtr.Zero) continue;\n SendMessageW(matchedButton, BM_CLICK, IntPtr.Zero, IntPtr.Zero);\n return \"dismissed:\" + matchedText + \":\" + (kind ?? \"unknown\");\n }\n return \"not-found\";\n }\n }\n}\n\"@\n }\n $unityPids = @(Get-Process -Name 'Unity' -ErrorAction SilentlyContinue | ForEach-Object { [int]$_.Id })\n if ($unityPids.Count -eq 0) { Write-Output 'not-found'; return }\n $tableJson = [Console]::In.ReadToEnd();\n Write-Output ([UnityOpenMcp.Dialogs.Dismisser]::TryDismiss([int[]]$unityPids, $tableJson))\n} catch {\n Write-Output ('error:' + $_.Exception.Message)\n}\n";
133
+ export declare const WINDOWS_DISMISS_PS_SCRIPT = "\n$ErrorActionPreference = 'Stop'\ntry {\n if (-not ([System.Management.Automation.PSTypeName]'UnityOpenMcp.Dialogs.Dismisser').Type) {\n Add-Type -TypeDefinition @\"\nusing System;\nusing System.Collections.Generic;\nusing System.Runtime.InteropServices;\nusing System.Text;\nnamespace UnityOpenMcp.Dialogs {\n public static class Dismisser {\n public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);\n [DllImport(\"user32.dll\")] static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);\n [DllImport(\"user32.dll\", CharSet=CharSet.Unicode)] static extern int GetWindowTextW(IntPtr hWnd, StringBuilder lpString, int nMaxCount);\n [DllImport(\"user32.dll\", CharSet=CharSet.Unicode)] static extern int GetClassNameW(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);\n [DllImport(\"user32.dll\")] static extern bool IsWindowVisible(IntPtr hWnd);\n [DllImport(\"user32.dll\")] static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);\n [DllImport(\"user32.dll\")] static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);\n [DllImport(\"user32.dll\", CharSet=CharSet.Unicode)] static extern IntPtr SendMessageW(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);\n const uint BM_CLICK = 0x00F5;\n static string Norm(string s) {\n var sb = new StringBuilder(s.Length);\n foreach (var ch in s) {\n if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {\n sb.Append(char.ToLowerInvariant(ch));\n }\n }\n return sb.ToString();\n }\n public static string TryDismiss(int[] unityPids, string tableJson) {\n var unityPidSet = new HashSet<uint>();\n for (int i = 0; i < unityPids.Length; i++) unityPidSet.Add((uint)unityPids[i]);\n var table = System.Text.Json.JsonSerializer.Deserialize<Types.Table>(tableJson);\n var candidates = new List<IntPtr>();\n var sb = new StringBuilder(512);\n EnumWindows((hWnd, lParam) => {\n if (!IsWindowVisible(hWnd)) return true;\n uint procId; GetWindowThreadProcessId(hWnd, out procId);\n if (!unityPidSet.Contains(procId)) return true;\n sb.Length = 0;\n GetWindowTextW(hWnd, sb, sb.Capacity);\n candidates.Add(hWnd);\n return true;\n }, IntPtr.Zero);\n foreach (var hWnd in candidates) {\n sb.Length = 0;\n GetWindowTextW(hWnd, sb, sb.Capacity);\n var title = sb.ToString();\n // The main Editor window title embeds project/scene names; never classify it.\n if (title.IndexOf(\" - Unity \", StringComparison.OrdinalIgnoreCase) >= 0) continue;\n var norm = Norm(title);\n string kind = null;\n foreach (var kv in table.kinds) {\n foreach (var frag in kv.Value.fragments) {\n if (norm.Contains(frag)) { kind = kv.Key; break; }\n }\n if (kind != null) break;\n }\n string[] tokens = null;\n if (kind != null) {\n if (table.blocked.Contains(kind)) return \"blocked:\" + kind;\n if (!table.kinds[kind].tokensSpecified) continue;\n tokens = table.kinds[kind].tokens;\n } else {\n if (table.genericTokens.Length == 0) continue;\n tokens = table.genericTokens;\n }\n IntPtr matchedButton = IntPtr.Zero;\n string matchedText = null;\n EnumChildWindows(hWnd, (hChild, lParam) => {\n sb.Length = 0;\n GetClassNameW(hChild, sb, sb.Capacity);\n if (sb.ToString() != \"Button\") return true;\n sb.Length = 0;\n GetWindowTextW(hChild, sb, sb.Capacity);\n var text = sb.ToString();\n var textNorm = Norm(text);\n foreach (var token in tokens) {\n if (textNorm.Contains(token)) { matchedButton = hChild; matchedText = text; return false; }\n }\n return true;\n }, IntPtr.Zero);\n if (matchedButton == IntPtr.Zero) continue;\n SendMessageW(matchedButton, BM_CLICK, IntPtr.Zero, IntPtr.Zero);\n return \"dismissed:\" + matchedText + \":\" + (kind ?? \"unknown\");\n }\n return \"not-found\";\n }\n }\n}\n\"@\n }\n $unityPids = @(Get-Process -Name 'Unity' -ErrorAction SilentlyContinue | ForEach-Object { [int]$_.Id })\n if ($unityPids.Count -eq 0) { Write-Output 'not-found'; return }\n $tableJson = [Console]::In.ReadToEnd();\n Write-Output ([UnityOpenMcp.Dialogs.Dismisser]::TryDismiss([int[]]$unityPids, $tableJson))\n} catch {\n Write-Output ('error:' + $_.Exception.Message)\n}\n";
131
134
  /**
132
135
  * The AppleScript template used for the macOS dismiss path. Exposed as a
133
- * function of the token table so tests can assert the script shape without
136
+ * function of the probe options so tests can assert the script shape without
134
137
  * launching osascript.
135
138
  *
136
- * Strategy: iterate every window of the Unity process, normalize its title,
137
- * classify it against the table, and click the first button whose normalized
138
- * label contains a policy token. If the kind is blocked, emit `blocked:<kind>`
139
- * without clicking. If Unity is not running OR no matching button exists, the
140
- * script reports `not-found`. Any AppleScript exception (e.g. Accessibility
141
- * permission not granted) is reported as `error:<msg>`.
139
+ * Strategy: iterate every window of the Unity process except the main Editor
140
+ * window (its title contains {@link UNITY_EDITOR_WINDOW_TITLE_MARKER}),
141
+ * classify it by its title normalized with `normLabel` (see
142
+ * {@link macosTitleMatch}), and act per kind as {@link focusDialogAction}
143
+ * decides from the shared policy table (see {@link macosDialogKindBlock}):
144
+ *
145
+ * - `focus` — the policy picks the kind's focused/default button
146
+ * ({@link DIALOG_FOCUSED_BUTTON_TOKENS}): press Return, but
147
+ * only when the window shows a button with that label.
148
+ * - `named` — the policy picks another button (e.g. Enter Safe Mode, or
149
+ * Quit/Cancel under `cancel`): click the first button whose
150
+ * normalized title equals a policy token, in priority order,
151
+ * or report `blocked:<kind>` when none is found. Return is
152
+ * never pressed, because it would click the focused button.
153
+ * - `blocked` — the policy blocks the kind: report `blocked:<kind>`.
154
+ * - `skip` — the policy declines the kind: no block; the window falls
155
+ * through to `not-found` so polling continues.
156
+ *
157
+ * Project Upgrade is always reported as `blocked` (never clicked), and
158
+ * unsaved scene changes have their own named-button block behind the opt-in.
159
+ * If Unity is not running or nothing matches, the script reports `not-found`.
160
+ * Any AppleScript exception (e.g. Accessibility permission not granted) is
161
+ * reported as `error:<msg>`.
142
162
  *
143
163
  * Requires the Terminal / `node` binary to have been granted Accessibility
144
164
  * permission in System Settings → Privacy & Security → Accessibility.
145
- *
146
- * The token table is JSON-escaped into the script header.
147
165
  */
148
166
  export declare function macosDismissAppleScript(opts: DismissProbeOptions): string;
167
+ /**
168
+ * AppleScript mirror of {@link normalizeDialogLabel}: keep ASCII letters and
169
+ * digits only. AppleScript string comparison (`is`, `contains`) ignores case
170
+ * by default, so the handler does not lowercase. Exported for tests.
171
+ */
172
+ export declare const MACOS_NORM_LABEL_HANDLER = "\n-- Mirror of normalizeDialogLabel: keep ASCII letters and digits only.\n-- (String comparison is case-insensitive by default, so no lowercasing.)\non normLabel(s)\n set out to \"\"\n repeat with c in (characters of s)\n set ch to contents of c\n set cid to id of ch\n -- \"id of\" yields a LIST for a character made of several code points\n -- (decomposed accents, emoji); comparing a list numerically would raise\n -- and abort the whole probe, so such characters are simply dropped.\n if class of cid is integer then\n if (cid >= 48 and cid <= 57) or (cid >= 65 and cid <= 90) or (cid >= 97 and cid <= 122) then set out to out & ch\n end if\n end repeat\n return out\nend normLabel\n";
173
+ /**
174
+ * How a focus-driven platform (macOS AppleScript, Linux xdotool) may act on a
175
+ * dialog of the given kind under the active policy. Derived from the shared
176
+ * policy table so every platform agrees:
177
+ *
178
+ * - `blocked` — the policy blocks the kind (e.g. Project Upgrade without its
179
+ * opt-in). Report `blocked:<kind>`, never click.
180
+ * - `skip` — the policy declines the kind (no preference tokens). Do
181
+ * nothing; try the next kind.
182
+ * - `focus` — the policy's first choice is the kind's focused button
183
+ * ({@link DIALOG_FOCUSED_BUTTON_TOKENS}): pressing Return is
184
+ * safe.
185
+ * - `named` — the policy's first choice is some other button (e.g. Enter
186
+ * Safe Mode, Quit, Cancel). Return would press the focused
187
+ * button instead, so only a named-button click may act.
188
+ *
189
+ * Pure; exported for tests.
190
+ */
191
+ export declare function focusDialogAction(kind: DialogKind, opts: Pick<DismissProbeOptions, "policy" | "allowProjectUpgrade" | "allowUnsavedSceneDismiss" | "allowVersionMismatch">): "blocked" | "skip" | "focus" | "named";
192
+ /**
193
+ * AppleScript title condition per focus-classified kind, over `wn` — the
194
+ * window title normalized by `normLabel`. The fragments are normalized
195
+ * (letters and digits only), and AppleScript `contains` ignores case but not
196
+ * spaces or punctuation, so matching the raw title would never fire
197
+ * (`"Enter Safe Mode?" contains "safemode"` is false). Non-Matching Editor
198
+ * matches the short `nonmatchingeditor` stem so both the hyphenated and the
199
+ * spaced spelling qualify. Exported for tests.
200
+ */
201
+ export declare function macosTitleMatch(kind: DialogKind): string;
202
+ /**
203
+ * AppleScript block for one dialog kind inside the window loop (`w` / `wt`),
204
+ * shaped by {@link focusDialogAction}:
205
+ *
206
+ * - `blocked` → `blocked:<kind>` when the title matches.
207
+ * - `skip` → a comment only; the window falls through.
208
+ * - `focus` → press Return on the focused button, when the window has a
209
+ * button whose normalized title equals the kind's focused
210
+ * token; otherwise fall through (no press).
211
+ * - `named` → for each policy token in priority order, click the first
212
+ * button whose normalized title equals it; if none matches,
213
+ * report `blocked:<kind>`. Never presses Return.
214
+ *
215
+ * Exact normalized equality (not substring) keeps a short token such as `no`
216
+ * from matching an unrelated label such as "Ignore".
217
+ *
218
+ * Pure; exported for tests.
219
+ */
220
+ export declare function macosDialogKindBlock(kind: DialogKind, opts: Pick<DismissProbeOptions, "policy" | "allowProjectUpgrade" | "allowUnsavedSceneDismiss" | "allowVersionMismatch">): string;
149
221
  /**
150
222
  * Reset the cached `xdotool` presence flag. Test-only — production code
151
223
  * never needs to call this.
@@ -157,6 +229,26 @@ export declare function _resetXdotoolPresenceForTests(): void;
157
229
  * containing metacharacters would silently change the match semantics.
158
230
  */
159
231
  export declare function regexEscapeForXdotool(s: string): string;
232
+ /**
233
+ * The `xdotool search --name` pattern for a normalized title fragment.
234
+ * Fragments are letters and digits only ("entersafemode"), while real titles
235
+ * carry spaces and punctuation ("Enter Safe Mode?"), so the pattern allows any
236
+ * run of non-alphanumerics between consecutive characters and spells each
237
+ * letter in both cases. That is the regex form of "the normalized title
238
+ * contains the fragment" and does not depend on xdotool's case handling.
239
+ * Any other character is escaped literally.
240
+ */
241
+ export declare function xdotoolTitlePattern(fragment: string): string;
242
+ /**
243
+ * What the Linux/X11 path does with a Unity-owned window of the given kind:
244
+ * {@link focusDialogAction} with `named` folded into `blocked`, because
245
+ * xdotool can only press Return on the focused (default) button — it cannot
246
+ * click a named one (e.g. `cancel` on the Safe Mode prompt, whose focused
247
+ * button is Ignore, is reported as blocked for a human).
248
+ *
249
+ * Pure; exported for tests.
250
+ */
251
+ export declare function linuxFocusAction(kind: DialogKind, opts: Pick<DismissProbeOptions, "policy" | "allowProjectUpgrade" | "allowUnsavedSceneDismiss" | "allowVersionMismatch">): "blocked" | "skip" | "focus";
160
252
  /**
161
253
  * Parse the single-token contract every platform-specific dispatcher writes
162
254
  * to stdout. Inspects the LAST non-empty line of stdout, not the whole
@@ -206,6 +298,8 @@ export declare function armSigkillEscalation(child: {
206
298
  * - `UNITY_OPEN_MCP_ALLOW_UNSAVED_SCENE_DISMISS=1` opts in to auto-dismissing
207
299
  * the "Unsaved changes to scene" modal (destructive under every policy —
208
300
  * off by default). See isUnsavedSceneBlocked.
301
+ * - `UNITY_OPEN_MCP_ALLOW_VERSION_MISMATCH=1` opts in to opening a project
302
+ * with a different Unity Editor version. Without it the dialog is blocked.
209
303
  * - `UNITY_OPEN_MCP_DISMISS_TIMEOUT_MS` overrides the overall timeout.
210
304
  * - `UNITY_OPEN_MCP_DISMISS_INTERVAL_MS` overrides the poll interval.
211
305
  *
@@ -219,6 +313,7 @@ export interface DismissConfig {
219
313
  policy: DialogPolicy;
220
314
  allowProjectUpgrade: boolean;
221
315
  allowUnsavedSceneDismiss: boolean;
316
+ allowVersionMismatch: boolean;
222
317
  }
223
318
  export declare function readDismissConfig(env?: NodeJS.ProcessEnv): DismissConfig;
224
319
  /**
@@ -233,6 +328,7 @@ export interface PollAndDismissOptions {
233
328
  policy: DialogPolicy;
234
329
  allowProjectUpgrade: boolean;
235
330
  allowUnsavedSceneDismiss: boolean;
331
+ allowVersionMismatch: boolean;
236
332
  /**
237
333
  * Override the platform — exposed for tests so the polling logic can be
238
334
  * exercised across all three OS branches without hopping process.platform.