unity-open-mcp 1.0.0 → 1.1.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.
- package/dist/batch-spawn.d.ts +26 -0
- package/dist/batch-spawn.js +152 -35
- package/dist/batch-spawn.js.map +1 -1
- package/dist/capabilities/build-capabilities.d.ts +10 -0
- package/dist/capabilities/build-capabilities.js +4 -0
- package/dist/capabilities/build-capabilities.js.map +1 -1
- package/dist/capabilities/capabilities-view.d.ts +48 -0
- package/dist/capabilities/capabilities-view.js +93 -0
- package/dist/capabilities/capabilities-view.js.map +1 -0
- package/dist/child-supervision.d.ts +34 -0
- package/dist/child-supervision.js +110 -0
- package/dist/child-supervision.js.map +1 -0
- package/dist/cli/cli.js +9 -0
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/commands.js +37 -4
- package/dist/cli/commands.js.map +1 -1
- package/dist/compressible-router.js +12 -1
- package/dist/compressible-router.js.map +1 -1
- package/dist/constants.d.ts +17 -7
- package/dist/constants.js +17 -7
- package/dist/constants.js.map +1 -1
- package/dist/dialog-dismiss.d.ts +11 -0
- package/dist/dialog-dismiss.js +59 -4
- package/dist/dialog-dismiss.js.map +1 -1
- package/dist/editor-wedge.d.ts +45 -0
- package/dist/editor-wedge.js +111 -0
- package/dist/editor-wedge.js.map +1 -0
- package/dist/event-stream.d.ts +6 -3
- package/dist/event-stream.js +62 -5
- package/dist/event-stream.js.map +1 -1
- package/dist/gate-error.d.ts +2 -0
- package/dist/gate-error.js +9 -0
- package/dist/gate-error.js.map +1 -1
- package/dist/instance-discovery.d.ts +23 -0
- package/dist/instance-discovery.js +47 -1
- package/dist/instance-discovery.js.map +1 -1
- package/dist/live-client.d.ts +23 -5
- package/dist/live-client.js +132 -31
- package/dist/live-client.js.map +1 -1
- package/dist/process-diagnostics.d.ts +4 -3
- package/dist/process-diagnostics.js +37 -7
- package/dist/process-diagnostics.js.map +1 -1
- package/dist/results.d.ts +8 -0
- package/dist/results.js +4 -1
- package/dist/results.js.map +1 -1
- package/dist/routers.js +4 -1
- package/dist/routers.js.map +1 -1
- package/dist/server.js +8 -2
- package/dist/server.js.map +1 -1
- package/dist/tool-hint.d.ts +36 -0
- package/dist/tool-hint.js +98 -0
- package/dist/tool-hint.js.map +1 -0
- package/dist/tool-router.d.ts +8 -1
- package/dist/tool-router.js +266 -20
- package/dist/tool-router.js.map +1 -1
- package/dist/tools/agent-capabilities.js +27 -1
- package/dist/tools/agent-capabilities.js.map +1 -1
- package/dist/tools/baseline-create.js +1 -1
- package/dist/tools/baseline-create.js.map +1 -1
- package/dist/tools/batch-execute.js +12 -1
- package/dist/tools/batch-execute.js.map +1 -1
- package/dist/tools/bridge-status.js +21 -1
- package/dist/tools/bridge-status.js.map +1 -1
- package/dist/tools/execute-csharp.js +11 -1
- package/dist/tools/execute-csharp.js.map +1 -1
- package/dist/tools/execute-menu.js +33 -2
- package/dist/tools/execute-menu.js.map +1 -1
- package/dist/tools/index.js +10 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/invoke-method.js +15 -1
- package/dist/tools/invoke-method.js.map +1 -1
- package/dist/tools/read-compile-errors.js +30 -9
- package/dist/tools/read-compile-errors.js.map +1 -1
- package/dist/tools/regression-check.js +2 -1
- package/dist/tools/regression-check.js.map +1 -1
- package/dist/tools/run-tests.js +11 -2
- package/dist/tools/run-tests.js.map +1 -1
- package/dist/tools/scan-all.js +2 -2
- package/dist/tools/scan-all.js.map +1 -1
- package/dist/unity-log.d.ts +16 -0
- package/dist/unity-log.js +60 -9
- package/dist/unity-log.js.map +1 -1
- package/package.json +1 -1
package/dist/tool-router.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { AssetModelCache, isCompressible, routeCompressible } from "./compressible-router.js";
|
|
2
2
|
import { listAssetsOffline, findReferencesOffline, dependenciesOffline } from "./offline.js";
|
|
3
|
-
import { resolveEditorLogPath, readLogTail, DEFAULT_LOG_TAIL_BYTES, detectStaleLog, detectStaleAssembly, parseLogAuthorship, compareLogAuthorship } from "./unity-log.js";
|
|
3
|
+
import { resolveEditorLogPath, readLogTail, DEFAULT_LOG_TAIL_BYTES, detectStaleLog, detectStaleAssembly, detectErrorsPredatingEdits, parseLogAuthorship, compareLogAuthorship } from "./unity-log.js";
|
|
4
|
+
import { toolHintReference } from "./tool-hint.js";
|
|
5
|
+
import { scanForFdExhaustion } from "./editor-wedge.js";
|
|
4
6
|
import { summarizeProjectHealth, extractProjectHealthIssues } from "./project-health.js";
|
|
5
7
|
import { countProjectAsmdefs, countAssembliesWithErrors } from "./asmdef-discovery.js";
|
|
6
8
|
import { buildCapabilities } from "./capabilities/build-capabilities.js";
|
|
@@ -8,11 +10,12 @@ import { RULE_CATALOG, FIX_CATALOG } from "./capabilities/rule-catalog.js";
|
|
|
8
10
|
import { GROUP_IDS, TOOL_GROUPS, AUTO_ACTIVATE_GROUPS, groupToTools, getGroup, } from "./capabilities/tool-groups.js";
|
|
9
11
|
import { recommendGroups, } from "./capabilities/intent-groups.js";
|
|
10
12
|
import { listRules } from "./capabilities/list-rules.js";
|
|
13
|
+
import { viewCapabilities } from "./capabilities/capabilities-view.js";
|
|
11
14
|
import { generateSkill } from "./skill/generate-skill.js";
|
|
12
15
|
import { getKnownClientKeys } from "./skill/client-paths.js";
|
|
13
16
|
import { ALL_TOOLS } from "./tools/index.js";
|
|
14
17
|
import { lockPath, readInstanceLock, classifyInstance, isPidAlive } from "./instance-discovery.js";
|
|
15
|
-
import { PORT_ENV_VAR,
|
|
18
|
+
import { PORT_ENV_VAR, EXPECTED_BRIDGE_WIRE_CONTRACT } from "./constants.js";
|
|
16
19
|
import { findUnityForProject, readProcessCommandLine } from "./running-unity.js";
|
|
17
20
|
// M31-optimizations Plan 1 / L14 — opt-in route-logging gate. Resolved once at
|
|
18
21
|
// module load into a boolean; the per-dispatch log sites read the boolean
|
|
@@ -51,7 +54,7 @@ const defaultHubBackend = {
|
|
|
51
54
|
setInstallPath: (path) => setInstallPath(path),
|
|
52
55
|
};
|
|
53
56
|
import { BATCH_TOOL_NAMES, ALWAYS_BATCH_TOOLS } from "./batch-spawn.js";
|
|
54
|
-
import { readProfileAndDetail, applyPaging, attachPagination, foldVerifyResult, parseResultBody, withResultBody, } from "./output-profile.js";
|
|
57
|
+
import { readProfileAndDetail, applyPaging, attachPagination, foldVerifyResult, parseResultBody, withResultBody, isOutputProfile, } from "./output-profile.js";
|
|
55
58
|
/**
|
|
56
59
|
* Single source of truth for the `_source` tag. Every route-tagging site goes
|
|
57
60
|
* through one of the two helpers below so live / offline / local stay
|
|
@@ -339,7 +342,25 @@ function groupLiveRefsByKind(paths) {
|
|
|
339
342
|
}
|
|
340
343
|
// Operator-facing "what to do next" hint for each coarse status. Kept short
|
|
341
344
|
// and action-oriented so the Validation Suite can render it inline.
|
|
342
|
-
function bridgeStatusNextStep(status) {
|
|
345
|
+
function bridgeStatusNextStep(status, wedge) {
|
|
346
|
+
if (status === "wedged") {
|
|
347
|
+
return wedge?.reason === "main_thread_wedged"
|
|
348
|
+
? "Unity's main thread is wedged — the HTTP listener still answers /ping, but the " +
|
|
349
|
+
"editor heartbeat has stopped, which is the signature of a MODAL DIALOG blocking " +
|
|
350
|
+
"the message pump (material/scene converters and several ExecuteMenuItem paths " +
|
|
351
|
+
"open one). This is NOT a compile failure and NOT a dead bridge assembly — a dead " +
|
|
352
|
+
"assembly has no listener at all. It cannot self-heal and no tool can dismiss the " +
|
|
353
|
+
"dialog: an operator must dismiss it in the Unity UI, after which queued calls " +
|
|
354
|
+
"resume. Prefer non-interactive APIs over unity_open_mcp_execute_menu for " +
|
|
355
|
+
"converter-style menus."
|
|
356
|
+
: "The Unity Editor hit the Bee build driver's file-descriptor exhaustion — the " +
|
|
357
|
+
"process is alive and the listener answers, but it can no longer BUILD: " +
|
|
358
|
+
"Library/ScriptAssemblies stops updating, so C# edits never take effect and " +
|
|
359
|
+
"unity_open_mcp_execute_csharp silently runs the previous assembly. Do not trust " +
|
|
360
|
+
"any result computed since. Call unity_open_mcp_read_compile_errors to confirm " +
|
|
361
|
+
"the editor_fd_exhaustion issue, save scene work, then restart the Editor " +
|
|
362
|
+
"(unity_open_mcp_restart_editor with confirm: true, or manually).";
|
|
363
|
+
}
|
|
343
364
|
switch (status) {
|
|
344
365
|
case "running":
|
|
345
366
|
return "Bridge is ready. Proceed with live-only MCP tools.";
|
|
@@ -371,7 +392,66 @@ function bridgeStatusNextStep(status) {
|
|
|
371
392
|
"Bridge) and confirm it is started.";
|
|
372
393
|
}
|
|
373
394
|
}
|
|
374
|
-
|
|
395
|
+
/**
|
|
396
|
+
* specs/feedback.md 2026-08-24 — wire-contract staleness block.
|
|
397
|
+
*
|
|
398
|
+
* The field report: a blocker fixed and shipped on 2026-08-17 (registry tools
|
|
399
|
+
* rejecting the transport envelope keys, which made `editor_status` uncallable
|
|
400
|
+
* with default args) recurred against an installed bridge that still reported
|
|
401
|
+
* `bridgeVersion "1.0.0"` — the same string the FIXED source reports, because
|
|
402
|
+
* the package semver does not move for a wire-contract fix. "Your install is
|
|
403
|
+
* stale" and "this regressed" were therefore indistinguishable at the call
|
|
404
|
+
* site, and the agent had no way to pick between reinstalling and filing a bug.
|
|
405
|
+
*
|
|
406
|
+
* `BridgeSession.WireContract` is bumped for every observable change to the
|
|
407
|
+
* request/response contract, so comparing it against
|
|
408
|
+
* {@link EXPECTED_BRIDGE_WIRE_CONTRACT} answers that question directly. A
|
|
409
|
+
* bridge that predates the field reports no `wireContract` at all — `bridge`
|
|
410
|
+
* is then null and `stale` is true, which is the correct reading (it is older
|
|
411
|
+
* than revision 1 by definition).
|
|
412
|
+
*
|
|
413
|
+
* Returns null when /ping was unreachable: there is no bridge to compare, and a
|
|
414
|
+
* bogus "stale" on an offline bridge would be worse than silence.
|
|
415
|
+
*/
|
|
416
|
+
function wireContractBlock(reachable, reported) {
|
|
417
|
+
if (!reachable)
|
|
418
|
+
return null;
|
|
419
|
+
const bridge = typeof reported === "number" ? reported : null;
|
|
420
|
+
const stale = bridge === null || bridge < EXPECTED_BRIDGE_WIRE_CONTRACT;
|
|
421
|
+
return {
|
|
422
|
+
bridge,
|
|
423
|
+
expected: EXPECTED_BRIDGE_WIRE_CONTRACT,
|
|
424
|
+
stale,
|
|
425
|
+
note: stale
|
|
426
|
+
? "The installed Unity bridge package is OLDER than this MCP server's wire " +
|
|
427
|
+
"contract" +
|
|
428
|
+
(bridge === null
|
|
429
|
+
? " (it does not report `wireContract` at all, so it predates revision 1)"
|
|
430
|
+
: ` (revision ${bridge} vs ${EXPECTED_BRIDGE_WIRE_CONTRACT})`) +
|
|
431
|
+
". A tool failing in a way the docs say is fixed is almost certainly " +
|
|
432
|
+
"this staleness, not a regression: reinstall/update the bridge package " +
|
|
433
|
+
"in Unity (Package Manager > the unity-open-mcp-bridge git URL) and " +
|
|
434
|
+
"retry before filing anything. bridgeVersion alone cannot tell you " +
|
|
435
|
+
"this — it does not move for a wire-contract fix."
|
|
436
|
+
: null,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
function bridgeStatusRecoveryHint(status, wedge) {
|
|
440
|
+
if (status === "wedged") {
|
|
441
|
+
return wedge?.reason === "main_thread_wedged"
|
|
442
|
+
? {
|
|
443
|
+
tool: "unity_open_mcp_editor_status",
|
|
444
|
+
reason: "Main thread wedged (likely a modal dialog): /ping answers but the " +
|
|
445
|
+
"editor heartbeat stopped. No tool can dismiss a modal — an operator " +
|
|
446
|
+
"must close it in the Unity UI, then editor_status confirms recovery.",
|
|
447
|
+
}
|
|
448
|
+
: {
|
|
449
|
+
tool: "unity_open_mcp_read_compile_errors",
|
|
450
|
+
reason: "editor_fd_exhaustion in the freshest Editor.log — the Editor answers " +
|
|
451
|
+
"HTTP but can no longer build, so C# edits never take effect. Confirm " +
|
|
452
|
+
"with read_compile_errors, then restart the Editor.",
|
|
453
|
+
};
|
|
454
|
+
}
|
|
375
455
|
if (status === "dead_bridge") {
|
|
376
456
|
return {
|
|
377
457
|
tool: "unity_open_mcp_read_compile_errors",
|
|
@@ -490,10 +570,21 @@ export class ToolRouter {
|
|
|
490
570
|
}
|
|
491
571
|
// Compact drill-down reads: offline-first for text-serialized assets, fall
|
|
492
572
|
// back to live bridge for binary formats. The compressible-router handles
|
|
493
|
-
// the source selection internally
|
|
573
|
+
// the source selection internally and tags the payload `_source`
|
|
574
|
+
// ("offline" when it was served from the disk parser without touching the
|
|
575
|
+
// bridge; error results tag their origin too — "local" for caller-side
|
|
576
|
+
// validation, "offline" for a failed offline-first attempt with the
|
|
577
|
+
// bridge down). Derive `_route` from that tag: stamping route:"live"
|
|
578
|
+
// unconditionally mislabeled offline-served reads as bridge responses —
|
|
579
|
+
// contradictory metadata that pointed an agent's diagnostics at the
|
|
580
|
+
// bridge for data it never produced.
|
|
494
581
|
if (isCompressible(toolName)) {
|
|
495
582
|
const result = await routeCompressible(toolName, args, live, this.modelCache, this.projectPath);
|
|
496
|
-
|
|
583
|
+
const source = parseResultBody(result)?._source;
|
|
584
|
+
const meta = source === "offline" || source === "local"
|
|
585
|
+
? { route: source }
|
|
586
|
+
: { route: "live" };
|
|
587
|
+
return injectRouteMeta(result, meta);
|
|
497
588
|
}
|
|
498
589
|
const canBatch = this.batch.isBatchTool(toolName);
|
|
499
590
|
const isPing = toolName === "unity_open_mcp_ping";
|
|
@@ -561,6 +652,11 @@ export class ToolRouter {
|
|
|
561
652
|
mode: "batch",
|
|
562
653
|
compiling: false,
|
|
563
654
|
isPlaying: false,
|
|
655
|
+
// feedback 2026-08-17 — provenance marker distinguishing this
|
|
656
|
+
// synthesized fallback (no probe was possible; the live route was
|
|
657
|
+
// unavailable) from the live route's verified probe answer
|
|
658
|
+
// (source:"probe" + asOf).
|
|
659
|
+
source: "unprobed",
|
|
564
660
|
_route: {
|
|
565
661
|
route: "batch",
|
|
566
662
|
fallbackReason: "live_unavailable",
|
|
@@ -700,6 +796,16 @@ export class ToolRouter {
|
|
|
700
796
|
// newest Library/ScriptAssemblies/*.dll mtime against Assets/**/*.cs source
|
|
701
797
|
// mtimes and surface staleAssembly when any source is newer than the DLL.
|
|
702
798
|
const staleAsm = detectStaleAssembly(this.projectPath);
|
|
799
|
+
// specs/feedback.md 2026-08-14 — "these errors may predate your edits".
|
|
800
|
+
// Three consecutive reads returned the same error block for code that had
|
|
801
|
+
// already been fixed, with no staleness signal: Editor.log's FILE mtime
|
|
802
|
+
// was fresh (asset imports keep appending) while the error BLOCK inside it
|
|
803
|
+
// was old, so the staleLog heuristic could not fire. The built-assembly
|
|
804
|
+
// mtime is the anchor that works — a cited source newer than the newest
|
|
805
|
+
// built DLL proves no compile has COMPLETED since the edit.
|
|
806
|
+
const errorsPredateEdits = errors.length > 0
|
|
807
|
+
? detectErrorsPredatingEdits(errors.map((e) => e.file), this.projectPath, staleAsm.dllMtimeMs)
|
|
808
|
+
: null;
|
|
703
809
|
// feedback-04-08-opus §2 — log-authorship cross-check. A batch run of a
|
|
704
810
|
// NEWER Unity writes a log whose errors (e.g. an API deprecation that is
|
|
705
811
|
// only an error on that version) CANNOT occur in the live editor. Parse the
|
|
@@ -729,7 +835,17 @@ export class ToolRouter {
|
|
|
729
835
|
: "no_errors_found";
|
|
730
836
|
const errorsMayNotApply = health.unhealthy &&
|
|
731
837
|
(!!staleLog || authorshipCheck.versionMismatch);
|
|
732
|
-
|
|
838
|
+
// feedback 2026-08-17 — a clean bill of health read from the ROTATED log
|
|
839
|
+
// is not a clean bill of health. When logSource is a prev_log_* value the
|
|
840
|
+
// resolver fell back to Editor-prev.log while a live Editor writes a
|
|
841
|
+
// different file; an agent reading only `status` got a false green. A
|
|
842
|
+
// no-errors read from a rotated log is explicitly `indeterminate`.
|
|
843
|
+
const prevLogRead = resolvedLog.reason.startsWith("prev_log_");
|
|
844
|
+
const status = errorsMayNotApply
|
|
845
|
+
? "stale_log"
|
|
846
|
+
: logVerdict === "no_errors_found" && prevLogRead
|
|
847
|
+
? "indeterminate"
|
|
848
|
+
: logVerdict;
|
|
733
849
|
// specs/feedback.md 2026-08-12 — partial-compile signal. Unity compiles
|
|
734
850
|
// assemblies in dependency order; when an early assembly fails the pipeline
|
|
735
851
|
// may not reach dependent ones, so the reported errors can be a SUBSET of
|
|
@@ -749,6 +865,54 @@ export class ToolRouter {
|
|
|
749
865
|
const assembliesWithErrors = partialCompileLikely
|
|
750
866
|
? countAssembliesWithErrors(errors.map((e) => e.file), this.projectPath)
|
|
751
867
|
: 0;
|
|
868
|
+
// Headline assembly. Precedence (most severe first):
|
|
869
|
+
// 1. errorsMayNotApply — stale log / version mismatch: the errors may
|
|
870
|
+
// not apply at all (feedback-fable-04-08 §4).
|
|
871
|
+
// 2. errorsPredateEdits — errors + a stale assembly: the error block
|
|
872
|
+
// was written by a compile that predates the on-disk edits (no
|
|
873
|
+
// compile has COMPLETED since: every built DLL is older than a cited
|
|
874
|
+
// source). The log-mtime staleness heuristic cannot see this — asset
|
|
875
|
+
// imports keep appending to Editor.log, so the FILE is fresh while
|
|
876
|
+
// the error BLOCK inside it is old. Say so in the headline rather
|
|
877
|
+
// than letting the agent re-read code it has already fixed
|
|
878
|
+
// (specs/feedback.md 2026-08-14).
|
|
879
|
+
// 3. feedback 2026-08-17 — errors + staleAssembly WITHOUT the
|
|
880
|
+
// cited-files proof: the whole-Assets scan still shows a source
|
|
881
|
+
// newer than the newest built DLL, so the error block may come from
|
|
882
|
+
// an earlier compile. The 2026-08-14 fix covered only the
|
|
883
|
+
// errorsPredateEdits shape; this extends the headline caveat to the
|
|
884
|
+
// staleAssembly shape (the field report's 12 stale CS errors had
|
|
885
|
+
// staleAssembly:true but the headline stated them as fact).
|
|
886
|
+
// 4. indeterminate (feedback 2026-08-17) — no errors, but read from the
|
|
887
|
+
// ROTATED log: not a verified clean bill of health.
|
|
888
|
+
// 5. the plain health headline.
|
|
889
|
+
const headline = errorsMayNotApply
|
|
890
|
+
? `${errors.length} error(s) in Editor.log, but the log appears ` +
|
|
891
|
+
"STALE or authored by a different Unity — these errors may NOT " +
|
|
892
|
+
"apply to the running editor. Do NOT act on them until a genuine " +
|
|
893
|
+
`recompile (${toolHintReference("unity_open_mcp_recompile_scripts")}) ` +
|
|
894
|
+
"confirms them."
|
|
895
|
+
: errorsPredateEdits
|
|
896
|
+
? `${health.headline} NOTE: these errors may PREDATE your edits — ` +
|
|
897
|
+
`${errorsPredateEdits.files.length} cited source file(s) are newer ` +
|
|
898
|
+
"than the newest built assembly, so no compile has completed since " +
|
|
899
|
+
"you changed them. Force a recompile " +
|
|
900
|
+
`(${toolHintReference("unity_open_mcp_recompile_scripts")}) and ` +
|
|
901
|
+
"re-read before acting on them."
|
|
902
|
+
: hasErrors && staleAsm.staleAssembly
|
|
903
|
+
? `${health.headline} NOTE: the error block may PREDATE the current ` +
|
|
904
|
+
`sources — ${staleAsm.newerSources.length} Assets/**/*.cs source(s) ` +
|
|
905
|
+
"are newer than the newest built assembly, so these errors may come " +
|
|
906
|
+
"from an earlier compile. Force a recompile " +
|
|
907
|
+
`(${toolHintReference("unity_open_mcp_recompile_scripts")}) and ` +
|
|
908
|
+
"re-read before acting on them."
|
|
909
|
+
: status === "indeterminate"
|
|
910
|
+
? "No errors found, but this is NOT a verified clean bill of " +
|
|
911
|
+
`health: the read came from the ROTATED log (${resolvedLog.reason === "prev_log_live_editor" ? "Editor-prev.log chosen because a live editor holds the project" : "Editor-prev.log fallback"}) ` +
|
|
912
|
+
"while a live Editor may be writing a different log. Confirm via " +
|
|
913
|
+
"the live bridge (unity_open_mcp_editor_status / a live tool " +
|
|
914
|
+
"probe) or re-read after the Editor settles."
|
|
915
|
+
: health.headline;
|
|
752
916
|
return sourceResult({
|
|
753
917
|
status,
|
|
754
918
|
// When the errors may not apply (stale log / version mismatch), the
|
|
@@ -758,14 +922,11 @@ export class ToolRouter {
|
|
|
758
922
|
// log verdict is preserved in `logVerdict` for callers that want the
|
|
759
923
|
// literal signal regardless of freshness.
|
|
760
924
|
unhealthy: errorsMayNotApply ? false : health.unhealthy,
|
|
761
|
-
headline
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
`group — ${TYPED_EDITOR_ACTIVATE_INSTRUCTION} first) confirms them.`
|
|
767
|
-
: health.headline,
|
|
768
|
-
...(errorsMayNotApply ? { logVerdict } : {}),
|
|
925
|
+
headline,
|
|
926
|
+
// Raw verdict preserved whenever the top-level status was rewritten
|
|
927
|
+
// (stale_log downgrade OR the indeterminate prev-log case), so callers
|
|
928
|
+
// can still read what the log literally said.
|
|
929
|
+
...(errorsMayNotApply || status === "indeterminate" ? { logVerdict } : {}),
|
|
769
930
|
errorCount: errors.length,
|
|
770
931
|
errors,
|
|
771
932
|
// specs/feedback.md 2026-08-12 — present only when there are errors AND
|
|
@@ -816,6 +977,26 @@ export class ToolRouter {
|
|
|
816
977
|
staleAssemblyNewerSources: staleAsm.newerSources,
|
|
817
978
|
}
|
|
818
979
|
: {}),
|
|
980
|
+
// specs/feedback.md 2026-08-14 — machine-readable sibling of the
|
|
981
|
+
// headline caveat above. Present ONLY when errors were returned AND at
|
|
982
|
+
// least one file they cite is newer than the newest built assembly, so
|
|
983
|
+
// the error block cannot reflect the current source. Distinct from
|
|
984
|
+
// `staleLogSuspected` (log-file mtime) and `staleAssembly` (whole
|
|
985
|
+
// Assets/ scan): this names the cited files the agent is about to
|
|
986
|
+
// re-read.
|
|
987
|
+
...(errorsPredateEdits
|
|
988
|
+
? {
|
|
989
|
+
errorsMayPredateEdits: true,
|
|
990
|
+
errorsMayPredateEditsFiles: errorsPredateEdits.files,
|
|
991
|
+
errorsMayPredateEditsHint: "These errors were parsed from a compile that ran BEFORE the " +
|
|
992
|
+
"listed files were last edited (each is newer than the newest " +
|
|
993
|
+
"Library/ScriptAssemblies/*.dll, so no compile has completed " +
|
|
994
|
+
"since). Do not re-read or re-fix that code on the strength of " +
|
|
995
|
+
"this block — force a recompile " +
|
|
996
|
+
`(${toolHintReference("unity_open_mcp_recompile_scripts")}) ` +
|
|
997
|
+
"and read again.",
|
|
998
|
+
}
|
|
999
|
+
: {}),
|
|
819
1000
|
// feedback-04-08-opus §2 — log authorship. Emitted whenever the header
|
|
820
1001
|
// was parseable (the live editor's long tail usually has no header, so
|
|
821
1002
|
// these stay absent in the common case). On a version mismatch the hint
|
|
@@ -945,6 +1126,12 @@ export class ToolRouter {
|
|
|
945
1126
|
? args.kind
|
|
946
1127
|
: undefined;
|
|
947
1128
|
const includePlanned = args.include_planned !== false;
|
|
1129
|
+
// specs/feedback.md 2026-08-24 — the tool surface defaults to `compact`
|
|
1130
|
+
// (the unfolded catalog is ~513 KB, which overflows a typical harness
|
|
1131
|
+
// per-result ceiling). `buildCapabilities` itself keeps returning the full
|
|
1132
|
+
// shape so its other caller (generate_skill) is unaffected; the fold is a
|
|
1133
|
+
// view applied here.
|
|
1134
|
+
const profile = isOutputProfile(args.profile) ? args.profile : "compact";
|
|
948
1135
|
// M18 Plan 2 / T18.2.3 — when the bridge is live, probe its compiled-
|
|
949
1136
|
// state tool inventory so per-group `available` reflects whether each
|
|
950
1137
|
// domain dependency compiled in (e.g. UNITY_OPEN_MCP_EXT_NAVIGATION).
|
|
@@ -970,7 +1157,11 @@ export class ToolRouter {
|
|
|
970
1157
|
fixes: FIX_CATALOG,
|
|
971
1158
|
availableBridgeTools,
|
|
972
1159
|
}, { kind, includePlanned });
|
|
973
|
-
return sourceResult(result,
|
|
1160
|
+
return sourceResult(viewCapabilities(result, {
|
|
1161
|
+
profile,
|
|
1162
|
+
page_size: typeof args.page_size === "number" ? args.page_size : undefined,
|
|
1163
|
+
cursor: typeof args.cursor === "string" ? args.cursor : undefined,
|
|
1164
|
+
}), "local");
|
|
974
1165
|
}
|
|
975
1166
|
async routeListRules(args) {
|
|
976
1167
|
const assetKind = typeof args.asset_kind === "string" ? args.asset_kind : undefined;
|
|
@@ -1474,8 +1665,52 @@ export class ToolRouter {
|
|
|
1474
1665
|
if (proc)
|
|
1475
1666
|
unityProcessPid = proc.pid;
|
|
1476
1667
|
}
|
|
1668
|
+
// specs/feedback.md 2026-08-14 — wedge detection, evaluated BEFORE the
|
|
1669
|
+
// existing token ladder because both wedges masquerade as one of its
|
|
1670
|
+
// healthy-looking outcomes.
|
|
1671
|
+
//
|
|
1672
|
+
// (a) main_thread_wedged. `dead_bridge` means "live PID + stale
|
|
1673
|
+
// heartbeat". A modal dialog produces exactly that (the heartbeat is
|
|
1674
|
+
// written from EditorApplication.update, which a blocked pump stops
|
|
1675
|
+
// running) — but a dead bridge ASSEMBLY cannot answer /ping at all,
|
|
1676
|
+
// because the listener lives in the assembly that failed to compile.
|
|
1677
|
+
// So a reachable /ping alongside a stale heartbeat rules out the
|
|
1678
|
+
// safe-mode diagnosis and identifies a stalled main thread instead.
|
|
1679
|
+
// (b) editor_fd_exhaustion. Process, heartbeat and /ping all survive a
|
|
1680
|
+
// dead build driver, so nothing in the existing ladder can see it.
|
|
1681
|
+
// One regex over the freshest log tail can (the same scan
|
|
1682
|
+
// read_compile_errors runs), and it only runs when the ladder would
|
|
1683
|
+
// otherwise report a healthy editor — the offline/stopped paths have
|
|
1684
|
+
// no editor to be wedged.
|
|
1685
|
+
let wedge = null;
|
|
1686
|
+
if (classification === "dead_bridge" && pingReachable) {
|
|
1687
|
+
wedge = {
|
|
1688
|
+
reason: "main_thread_wedged",
|
|
1689
|
+
detail: "The instance lock's heartbeat is stale (the editor main thread stopped " +
|
|
1690
|
+
"ticking) but the bridge listener still answers /ping. A failed bridge " +
|
|
1691
|
+
"assembly would take the listener down with it, so this is a blocked main " +
|
|
1692
|
+
"thread — almost always a modal dialog — not Safe Mode.",
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
else if (pingReachable && connected) {
|
|
1696
|
+
const fd = scanForFdExhaustion(this.projectPath, this.resolveLiveEditorPid(), this.globalLogPathOverride);
|
|
1697
|
+
if (fd.present) {
|
|
1698
|
+
wedge = {
|
|
1699
|
+
reason: "editor_fd_exhaustion",
|
|
1700
|
+
detail: "The freshest Editor.log carries the Bee build-driver file-descriptor " +
|
|
1701
|
+
"exhaustion signature. The Editor answers HTTP but cannot build: " +
|
|
1702
|
+
"Library/ScriptAssemblies stops updating, so C# edits never take effect " +
|
|
1703
|
+
"and execute_csharp keeps running the previous assembly.",
|
|
1704
|
+
...(fd.logPath ? { logPath: fd.logPath } : {}),
|
|
1705
|
+
...(fd.raw ? { raw: fd.raw } : {}),
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1477
1709
|
let status;
|
|
1478
|
-
if (
|
|
1710
|
+
if (wedge !== null) {
|
|
1711
|
+
status = "wedged";
|
|
1712
|
+
}
|
|
1713
|
+
else if (classification === "dead_bridge") {
|
|
1479
1714
|
status = "dead_bridge";
|
|
1480
1715
|
}
|
|
1481
1716
|
else if (compiling) {
|
|
@@ -1517,7 +1752,13 @@ export class ToolRouter {
|
|
|
1517
1752
|
// Safe Mode / compile failure", not the generic "stopped" the bare
|
|
1518
1753
|
// status used to suggest.
|
|
1519
1754
|
classification,
|
|
1520
|
-
recoveryHint: bridgeStatusRecoveryHint(status),
|
|
1755
|
+
recoveryHint: bridgeStatusRecoveryHint(status, wedge),
|
|
1756
|
+
// specs/feedback.md 2026-08-14 — present ONLY when the editor is wedged.
|
|
1757
|
+
// `classification` keeps mirroring the instance lock verbatim (healthy |
|
|
1758
|
+
// reloading | dead_bridge | gone) so that contract is unchanged; the
|
|
1759
|
+
// wedge is carried by the tool's own `status` token plus this block, and
|
|
1760
|
+
// `ready` is false either way.
|
|
1761
|
+
...(wedge !== null ? { wedged: wedge } : {}),
|
|
1521
1762
|
instance: {
|
|
1522
1763
|
lockPath: lockOnDisk,
|
|
1523
1764
|
classification,
|
|
@@ -1535,10 +1776,15 @@ export class ToolRouter {
|
|
|
1535
1776
|
isPlaying: pingBody?.isPlaying ?? null,
|
|
1536
1777
|
unityVersion: pingBody?.unityVersion ?? null,
|
|
1537
1778
|
bridgeVersion: pingBody?.bridgeVersion ?? null,
|
|
1779
|
+
wireContract: pingBody?.wireContract ?? null,
|
|
1538
1780
|
mode: pingBody?.mode ?? null,
|
|
1539
1781
|
}
|
|
1540
1782
|
: { reachable: false },
|
|
1541
|
-
|
|
1783
|
+
// specs/feedback.md 2026-08-24 — null when /ping was unreachable; a
|
|
1784
|
+
// `stale: true` block is the signal to reinstall the Unity package
|
|
1785
|
+
// rather than file a regression.
|
|
1786
|
+
wireContract: wireContractBlock(pingReachable, pingBody?.wireContract),
|
|
1787
|
+
nextStep: bridgeStatusNextStep(status, wedge),
|
|
1542
1788
|
};
|
|
1543
1789
|
// bridge_status never reports an error — even a stopped bridge is a
|
|
1544
1790
|
// successful status read. _source=local because the synthesis happens in
|