113 Commits

Author SHA1 Message Date
Shutong Wu 36cbe62fe1 refactor(asset-gen): dedupe generate_* tool shell (CodeRabbit review)
status / cancel / list_providers were near-identical across GenerateAudio,
GenerateImage and GenerateModel (differing only by a kind label + poll
interval), and NormalizeOutputFolder was a verbatim triple copy.

- New AssetGenToolHelpers.{Status,Cancel,ListProviders} — the three tools now
  delegate, passing their kind label / poll interval.
- NormalizeOutputFolder moved to AssetGenPaths (it already lived on
  TryGetAssetsFolder).
- Behaviour preserved (image `remove_background` arm kept; provider-list
  filtering unchanged). Net -120 lines of duplication.

Verified: EditMode 1167 tests / 0 failures + in-editor smoke (audio/image/model
list_providers, status/cancel error paths, image remove_background all intact).

Claude-Session: https://claude.ai/code/session_015KYy51gwBuhDuLZXXoqc98
2026-07-13 10:26:31 -07:00
Shutong Wu 8c0a5dfe0d feat(asset-gen): model catalog + generate_audio tool + default-model wiring
Phases 2-3. Adds a curated AssetGenModelCatalog (image/3D/audio models with
price/duration/use-case metadata; defaults reference the adapter constants so the
panel default equals what an omitted model resolves to). Adds the generate_audio
MCP tool + CLI, threads a per-(kind,provider) selected-model pref so a GUI choice
becomes the default generate_* uses when no model is passed, and makes
Tripo/Meshy consume req.Model. Per-provider prefs (not per-type) so disjoint model
lists never clobber. Compiles on 2021.3 floor; Python + adapter/catalog tests green.

Claude-Session: https://claude.ai/code/session_01GCxmdd4qo7MG6J4M6WcT9Y
2026-07-12 11:20:16 -07:00
Shutong Wu e677663497 feat(editor): embed Ocean brand mark in Editor UI + setup polish
Add the Ocean split-cube brand mark to the Editor UI and land three
low-risk installation-flow improvements.

Logo:
- OceanMark: reusable UI Toolkit control drawing the mark in Painter2D
  vector (geometry transcribed from logo-mark.svg, brand colors baked in),
  crisp at any DPI with no new package dependency. Painter2D is 2022.1+,
  so it is guarded by #if UNITY_2022_1_OR_NEWER with a raster fallback
  (package-icon.png) on the 2021.3 floor.
- Embedded in the main window header (#header-left group) and the
  setup-wizard header.
- Ship package-icon.png inside the package (listing icon + 2021.3 fallback).

Installation polish:
- One-click uv install: UvInstaller builds the official installer command
  per platform; setup wizard runs it off-thread with a confirm dialog and
  re-checks dependencies.
- Claude CLI auto-discovery: consolidate the weaker resolver into
  ExecPath.ResolveClaude() and add the ~/.claude/local (migrate-installer)
  location both resolvers missed.
- Setup wizard clarity: success/next-step copy after configuring clients.

Tests: EditMode coverage for the geometry/color mapping, per-platform uv
command, and the Claude override contract.

Verified: compiles clean on 2021.3.45f2 (fallback path) and 6000.4.11f1
(Painter2D path).
2026-07-02 22:09:23 -07:00
Shutong Wu 36fcefbcbf Merge remote-tracking branch 'upstream/beta' into revamp/brand-distribution-analytics
# Conflicts:
#	README.md
2026-06-30 11:33:02 -07:00
Shutong Wu 39eb562d9a Harden asset generation file paths 2026-06-29 09:15:59 -07:00
Shutong Wu 5ad4e5eb55 feat(asset-gen): Blender -> Unity handoff row in the Asset Gen tab
Informational row (not a keyed provider) with a best-effort "Blender app detected"
status and a pointer to the blender-to-unity workflow / import_model_file. BlenderMCP
runs in the AI client and isn't detectable from Unity, so this reports only the
locally installed Blender app.

- BlenderDetection helper: checks well-known install paths + PATH per platform; the
  pure DetectIn(candidates, exists) core is unit-tested with a fake predicate.
- McpAssetGenSection: AddBlenderHandoffRow() appended after the provider rows.
- README: note the tab's best-effort detection + that BlenderMCP lives in the AI client.

Verified: package compiles clean; EditMode 972/1018 pass, 0 failures (4 new
BlenderDetection tests) via tools/local_harness.py.

Claude-Session: https://claude.ai/code/session_015DAUrMR5UaSEzEn2wNPrEP
2026-06-28 23:19:59 -07:00
Shutong Wu 2efb786042 fix(asset-gen): security hardening + provider correctness + local image_path
Security review + code review of the asset-gen feature surfaced concrete issues;
this fixes them and adds regression tests (request-shaping layer, FakeHttpTransport).

Security
- SafeZipExtractor enforces an extension allowlist; ModelImportPipeline passes an
  inert model/texture allowlist so a provider archive can't drop a .cs/.dll under
  Assets/ and have the Editor compile/load it (code execution on import).
- AssetGenJobManager refuses non-http(s) download URLs before fetching
  (file:// SSRF / local-file read into the project).

Provider correctness
- Meshy image->3D polls /openapi/v1/image-to-3d/{id} (was the v2 text URL).
- Meshy text->3D honors texture=true via the preview->refine two-phase flow.
- OpenRouter image->image attaches the reference image (content image_url part).
- fal image->image uses the /edit endpoint + image_urls array; width/height
  forwarded as image_size.
- Sketchfab search forwards categories/count/cursor/downloadable; preview doc
  corrected (returns metadata, not a base64 thumbnail).
- Job import calls AssetDatabase.Refresh() before importing a freshly written file.

Local image input (image_path)
- New LocalImage helper; image_path is read and sent inline as a base64 data URI
  for Meshy / fal / OpenRouter. Tripo rejects local images with a clear error
  (needs a hosted image_url; its upload flow is not wired).

Cleanup (no behavior change)
- Shared AssetGenPaths + ProviderHttp helpers, HttpResult.Ok, MissingKeyMessage,
  cached glTFast probe, dead-field / per-frame-alloc removal, CLI _emit.

Docs: README + manual-verification updated (image_path support; transparency is
import-flag-only; width/height fal-only).

Verified: package compiles clean; Python 1306 passed / 3 skipped. Meshy refine,
fal /edit, and image_path data-URI paths are unit-tested at the request layer
only -- live smoke per provider (real keys) still pending.

Claude-Session: https://claude.ai/code/session_015DAUrMR5UaSEzEn2wNPrEP
2026-06-28 21:30:00 -07:00
Shutong Wu d6604a571e feat(asset-gen): scaffold asset_gen tool group + non-secret prefs (Phase 0)
- Register 'asset_gen' tool group (off by default, parity with vfx/animation)
- EditorPrefKeys.AssetGen.* consts (non-secret config only; keys go to secure store)
- AssetGenPrefs helper (provider/format/output-root/normalize/enabled) + EditMode tests
- Python scaffold test asserts group present and disabled by default

Claude-Session: https://claude.ai/code/session_01Tjpb5gYgUe2AUJuRdXr7Lv
2026-06-28 19:25:20 -07:00
Shutong Wu 69eb51c661 chore(revamp): checkpoint WIP before testing feat/3d-asset-generation
README-zh.md edits + .meta/uv.lock churn on brand-distribution-analytics, committed
(not stashed) so the feature branch can be checked out in the main worktree for editor
testing. Untracked .agents/ and AGENTS.md intentionally left out.

Claude-Session: https://claude.ai/code/session_01Tjpb5gYgUe2AUJuRdXr7Lv
2026-06-28 11:11:59 -07:00
Shutong Wu afa447ef38 fix(client): configure Kilo Code with its kilo.jsonc MCP format (#1120)
Kilo Code v7.0.33+ moved MCP config out of the VS Code extension's
globalStorage/mcp_settings.json to a CLI-style kilo.jsonc under ~/.config/kilo,
with a new schema (https://app.kilo.ai/config.json): an "mcp" container,
type:"remote" for HTTP servers (type:"local" for stdio), and an "enabled" flag.
Writing the legacy mcpServers / type:"http" / disabled config left the server
showing as stdio + disabled.

- KiloCodeConfigurator targets ~/.config/kilo/kilo.jsonc on every OS and declares
  the new format (mcp container, type:remote/local, enabled:true, $schema)
- Generalize the per-client HTTP "type" override: replace the UsesStreamableHttpType
  bool with McpClient.HttpTypeValue (Cline/Roo => "streamableHttp", Kilo => "remote",
  default "http"); add StdioTypeValue, ServerContainerKey and SchemaUrl fields
- ConfigJsonBuilder honors ServerContainerKey ("mcp") and writes a root $schema;
  JsonFileMcpConfigurator.CheckStatus/Unregister read/remove from the configured
  container so status detection and teardown work for Kilo
- Replace StreamableHttpTypeTests with ClientConfigFormatTests covering Kilo's
  remote/mcp/enabled format, Cline's streamableHttp, and generic http

Verified: 5 new tests + 13 existing config tests pass (EditMode, Unity 2021.3).
2026-06-14 21:32:04 -07:00
Shutong Wu eb9a4d6094 Merge remote-tracking branch 'upstream/beta' into chore/triage-quickwins-and-e2e 2026-06-14 11:49:30 -07:00
Shutong Wu 6e1a87e4bb fix(stdio): retry same port on bind race instead of silent fallback (#1173)
After a domain reload the previous TcpListener's OS socket is not always
released by the time Start() rebinds, so listener.Start() throws
AddressAlreadyInUse. The old catch silently switched to a new port via
DiscoverNewPort(), which stranded the Python client (pinned to the configured
port) on the orphan listener and returned busy/timeout indefinitely.

Instead, keep the configured port and fail the start attempt WITHOUT blocking;
the reload handler's async resume schedule and the editor-idle retry re-invoke
Start() on the same port within ~1s, by which point the OS has released it.
Only after the port stays busy past a short window (PortManager.
ShouldAbandonBusyPort, 3s) is it treated as a foreign occupant and the bridge
falls back to a new port — loudly (McpLog.Warn) instead of the previous
debug-only switch.

This keeps Stop()'s 500ms wait intact (no #787 backgrounded-stall regression)
and is platform-agnostic (the race also reproduces on macOS, per #1173).

Also classify ObjectDisposedException from a torn-down NetworkStream as a benign
client disconnect so it no longer spams the Console as a red error (#1187).
2026-06-13 15:57:01 -07:00
Shutong Wu 7b73f3e538 Merge remote-tracking branch 'upstream/beta' into chore/triage-quickwins-and-e2e 2026-06-07 22:22:51 +08:00
Shutong Wu 21953a09f2 fix: resolve unqualified generic names in unity_reflect; fix URP/ProBuilder EditMode test assumptions
UnityTypeResolver.FindCandidates treated a short name as ambiguous when an internal type shared it (richer assembly sets), so unqualified generics like List<T>/Dictionary<TKey,TValue> failed to resolve while fully-qualified names worked. Add DisambiguateByIdentity: de-dupe by FullName, then prefer a single public type, then a single core/BCL type, narrowing only to a unique survivor so genuine clashes still report ambiguous.

Fix EditMode tests that encoded built-in-pipeline/contract assumptions and only failed under URP + ProBuilder (the package test project uses the built-in pipeline and lacks ProBuilder, so these were never exercised):
- ManageGraphics negative tests read result["error"] (ErrorResponse has no "message" field); feature_add uses the handler's "type" param, not "feature_type"
- ManageMaterialProperties uses the pipeline-appropriate color property (_BaseColor on URP/HDRP)
- MCPToolParameterTests asserts pipeline-appropriate shader name and reads _Smoothness on URP/HDRP
- ManageProBuilder get_mesh_info test requests include:"faces"

Verified in TestbedMCP (URP 17.2 + ProBuilder 6.0.9): full EditMode suite 887 passed / 0 failed / 46 skipped.
2026-06-07 22:20:47 +08:00
cyanxwh 1d8bda3155 fix: skip unsafe Fusion serialization types 2026-05-15 12:00:26 +08:00
Shutong Wu 1fba42998c Update0503
1.Add Compat based scripts revolving around UnityCompatShims.cs, that will document our current API Compatibility changes in several files.
2.Add custom screenshot folder selection
2026-05-03 18:16:10 -04:00
Shutong Wu d7972db2c9 Patch0426
1. fix(#1068): block NuGet System.CodeDom conflict via
  overrideReferences
  2. fix(#1062): add reflection branch for Unity 6.6 InstanceIDToObject
  deprecation
  3. refactor: merge EditorObjectResolver into UnityObjectIdCompat (could
  squash into prev)
  4. fix(#1065): cache McpLogRecord.IsEnabled to allow ThreadPool reads
  5. fix(#1070): emit stdio entry from OpenCodeConfigurator
  6. fix: rewrite localhost→127.0.0.1 in NormalizeBaseUrl (supersedes
  #1057)
2026-04-26 20:51:42 -04:00
Shutong Wu f69acf0fb1 Cleanup 2026-04-12 16:41:03 -04:00
Tomicz Engineering LLC deea7d2a56 Replace reflection with version-gated conditional compilation.
Addresses review feedback on the Unity 6.5 GetInstanceID migration:

- UnityObjectIdCompatExtensions: drop the reflective method lookup in
  favor of a simple #if UNITY_6000_5_OR_NEWER / #else split calling
  GetEntityId() or GetInstanceID() directly. Also wrap the class in
  the MCPForUnity.Runtime.Helpers namespace.
- UnityTypeConverters: remove the reflective EntityIdToObject probe and
  call EditorUtility.EntityIdToObject(EntityId) directly under the same
  version gate. Serialize entityID as EntityId.ToULong() rather than
  ToString(), since Unity's docs explicitly warn that the textual form
  is not a stable serialization contract.
- Drop #pragma warning disable 0619 from 22 files that no longer make
  any direct calls to obsolete APIs. The remaining 7 files still need
  it (FindObjectsOfType, InstanceIDToObject fallback) and are left as-is
  — those deprecations are out of scope for this PR.
- Add the MCPForUnity.Runtime.Helpers using to every file that calls
  GetInstanceIDCompat() now that the extension method lives in a
  namespace.
2026-04-11 22:55:45 +02:00
Tomicz Engineering LLC 1ad440704c Move UnityObjectIdCompatExtensions into Runtime assembly.
Place the compatibility extension in MCPForUnity.Runtime so Runtime/Serialization can resolve GetInstanceIDCompat, fixing Unity 2022 compile errors from assembly visibility.
2026-04-09 12:19:45 +02:00
Tomicz Engineering LLC 8a89a03eda Migrate Unity object ID usage for Unity 6.5 compatibility.
Replace direct GetInstanceID calls with a compatibility helper and update serialization to handle EntityId-era identifiers, so the package compiles on Unity 6.5 while preserving behavior on older versions.
2026-04-09 11:59:25 +02:00
dsarno a3465587d2 fix: Unity 2021.3 compat — compile errors, Mono crash, 19 test failures (#1036) 2026-04-05 18:07:06 -07:00
Shutong Wu f476d68d82 Update on issue#1020 2026-04-03 00:22:47 -04:00
Shutong Wu e15a377980 Update through field deployment - bug fix / doc update / UX change 2026-03-31 01:08:27 -04:00
EternalSunLhx 6511e9ed35 When there are multiple Editor folders, fix the issue by retrieving the index of the last Editor. 2026-03-28 11:15:30 +08:00
Shutong Wu 475e5b24ef Restore original ref on incompatible assignments
ComponentOps: preserve the original objectReferenceValue before trying to assign found sprites; if Unity rejects the assignment (resulting in a null reference), restore the original value and return a clear error indicating the sprite name or fileID was incompatible with the property type.

ManageScriptableObject: use TryGetValue to read the legacy "ref" token so non-JObject tokens (e.g. string GUIDs) are preserved; treat ref or value as the resolve token and return a structured error when neither is present. These changes improve robustness when object references are incompatible or when legacy ref formats are used.
2026-03-22 21:58:23 -04:00
Shutong Wu 5a46319f50 Update 2026-03-22 20:53:25 -04:00
Shutong Wu fd21da52b9 Merge branch 'beta' into feat/scene-view-screenshot-capture-v2 2026-03-15 19:35:26 -04:00
Shutong Wu c9f88d03fa Update
1. Intergrate some parameters
2. Update the docs
2026-03-15 19:34:15 -04:00
Shutong Wu c30405417c Update 2026-03-15 17:27:37 -04:00
Shutong Wu 0b56f0a8a9 Initial Commit 2026-03-15 16:13:39 -04:00
jfjia 49e4d6ce23 fix(scene): tighten scene view screenshot validation 2026-03-13 17:38:31 +08:00
jfjia 12993541fc fix(camera): normalize screenshot filename paths 2026-03-13 17:17:44 +08:00
jfjia 0b135bee15 fix(camera): sanitize reserved screenshot filenames 2026-03-13 17:15:08 +08:00
jfjia 3a3ad7d90f fix(camera): harden scene view screenshot filenames 2026-03-13 15:51:51 +08:00
jfjia c3b62a88c0 chore(camera): clarify scene view capture constraints 2026-03-12 12:08:42 +08:00
jfjia c85cbfe104 feat(camera): add scene view screenshot capture support 2026-03-12 11:54:11 +08:00
Shutong Wu 90daff6b4c Fix on config and log 2026-03-11 14:50:53 -04:00
Shutong Wu f86a4d9504 Bug fix and thread-safe log feature 2026-03-11 01:29:27 -04:00
Shutong Wu e4b131bbb8 Update on three different changes
1. Add McpLog that could log mcp calls and errors under Asset/
2. Solve issues #816 via incluging str in annotation, extracted AssignedObjectreference() helper that verify assignments and resolve component types from gameobjects.
2026-03-11 01:09:38 -04:00
Shutong Wu b61abdc968 fix: use unchecked cast and log exceptions in GetSpriteFileId
- unchecked((long)targetObjectId) prevents OverflowException for large ulong fileIDs
- Replace silent catch with McpLog.Warn including sprite name and instanceID

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 23:21:04 -04:00
Shutong Wu 3e1bc51cca Merge remote-tracking branch 'origin/beta' into pr-873-update 2026-03-10 22:56:47 -04:00
Shutong Wu 7fd9af2d59 fix: resolve compilation error and silent fallthrough in atlas sprite resolution
- Replace non-existent ParamCoercion.CoerceLong with fileIdToken.Value<long>()
- Add error return when fileID matches no sprite (consistent with spriteName branch)
- Remove extra blank line before GetSpriteFileId

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 22:55:10 -04:00
Shutong Wu b5b58b97a4 [Feature] Manage_graphics (#890)
* Initial update

* Update with docs and fix

* Update on Skybox

* Bug fix and compatability issue

* Update

* Resolve EntityIdToInstance and InstanceIdToObject conflict

* update to revert the changes

Seems EntityId is implicitly casted to Int so no need to prevent it.
2026-03-08 14:40:32 -04:00
jfjia f8d4915f28 feat(mcpforunity): support atlas sprite resolution by guid+spriteName/fileID 2026-03-06 18:19:02 +08:00
Shutong Wu 852703d842 [Feature] Meta tool: toggle tool context in realtime (#854)
* Initial update on tool list update

* clean up and doc update

* Update for Stdio mode

* UI change and doc update

* fix based on audit

* Update manage_tools.py

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-03 00:07:14 -05:00
Shutong Wu 6c7accfc16 Various Fixes
1. Chinese Readme and Manifest order fix that might tackle Issue#847
2. Some fixes on Render and RenderMaterial, making sure the ParticleEffect material will use RP-according materials.
2026-02-28 23:22:52 -05:00
Shutong Wu 6e7bd42ec4 Update on skill/UI/camera (#818)
* Animation First PR

* Update ClipPresets to take account of local offset

* Update MCPForUnity/Editor/Tools/Animation/ClipCreate.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update for AI fix

* Temp Update

* update

* update

* update

* Initial update

* Delete 2026-02-09-implement-the-following-plan.txt

* update

* Remove scene generator, 3D gen, and unrelated files from PR

Remove files that don't belong in this camera/screenshot PR:
- Scene generator pipeline (Server/src/scene_generator/*)
- Manage3DGen tool (C# + Python)
- Generated test scripts (TestProjects/*/Scripts/*)
- Root-level docs and scripts (ProposedTable.md, system-prompt.md, start-scene-builder.*)
- Revert MCPForUnity.Editor.asmdef and CLAUDE.md to beta

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Remove ObjectTransformHistory from PR

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update pyproject.toml

* Update MCPForUnity/Editor/Tools/GameObjects/GameObjectLookAt.cs

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

* Update Server/src/services/tools/manage_scene.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* update based on ai feedback

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
2026-02-24 00:53:23 -05:00
dsarno 79e6c917f0 Fix stdio bridge stalls when Unity is backgrounded during domain reload (#787)
* Fix StdioBridgeHost zombie state after domain reload (#785)

After domain reload, the bridge could accept TCP connections but never
process commands. Three defensive fixes address the plausible failure
vectors:

1. Stale client cleanup: when a new client connects, close all other
   active clients (in stdio there is only one server). Forces hung
   ReadFrameAsUtf8Async to throw and exit cleanly.

2. ProcessCommands self-healing: track consecutive TCS timeouts. After
   2+ consecutive, force re-register ProcessCommands on
   EditorApplication.update and reset the reentrancy guard.

3. Stale command eviction: commands stuck with IsExecuting=true for
   more than 60s are evicted from the queue with an error result.

Also adds always-on diagnostic logging at client connect/disconnect
with active client counts.

Includes E2E reconnection tests that verify both abrupt disconnect
recovery and stale client cleanup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix stdio bridge stalls when Unity is backgrounded during domain reload

- Add QueuePlayerLoopUpdate after command enqueue so ProcessCommands fires
  even when Unity is backgrounded (mirrors HTTP RequestMainThreadPump)
- Keep ProcessCommands permanently registered on EditorApplication.update
  to eliminate the registration gap between Stop and Start during reload
- Replace blocking retry loop in Start() (Thread.Sleep x 10) with single
  attempt + port-switch fallback; async retries handled by reload handler
- Replace fire-and-forget TryStartBridgeImmediate with async retry loop
  (6 attempts with backoff: 0s, 1s, 3s, 5s, 10s, 30s) matching HTTP
- Fix macOS port conflict: use ExclusiveAddressUse in both the real
  listener (CreateConfiguredListener) and the port probe (IsPortAvailable)
  to prevent AssetImportWorkers from binding the same port via SO_REUSEADDR
- Add PortManager unit tests (5 tests including macOS SO_REUSEADDR case)

Fixes #785

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add resource URI reference table to unity-mcp-skill

Adds a prominent 'Do NOT Guess' section with a URI lookup table near
the top of the skill, so LLMs use exact URIs instead of fabricating
similar-looking ones.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address PR review feedback

- Collapse double lock in HandleClientAsync (add + count in one lock)
- Add CancellationToken to ResumeStdioWithRetriesAsync so retries abort
  on editor quit or subsequent domain reloads
- Fix 'gameobject' → 'GameObject' capitalization in skill URI table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address CodeRabbit review feedback

- PortManagerTests: save/restore on-disk port files in SetUp/TearDown
  to prevent DiscoverNewPort from mutating persistent state
- StdioBridgeReconnectTests: replace blocking Connect with ConnectAsync
  + Wait(ConnectTimeoutMs) so CI tests cannot hang indefinitely

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 19:49:42 -08:00
David Sarno c3665d1d3f Move cache fields above methods and avoid redundant ShouldForceUvxRefresh calls
Address CodeRabbit nitpicks: relocate static cache fields above the
methods that use them for readability, extract GetCachedOfflineProbeResult()
so GetUvxDevFlags()/GetUvxDevFlagsList() evaluate ShouldForceUvxRefresh()
only once per call instead of twice.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 20:50:11 -08:00