fix(bridge): listener watchdog, fixed-time auth, agent value coercion, auto-tick - #182
Merged
Merged
Conversation
…alues
Agents routinely send a structured value as a JSON-encoded string —
"[1,2,3]" for a Vector3, "{\"m_Mass\":0.17}" for a nested object. Strict
token typing rejected those with an error that reads as though the value
itself were wrong, so the agent has no way to tell quoting from a bad
field.
ApplyToken — the single funnel for top-level, nested and array-element
values — now re-parses a string token when the target cannot hold a raw
string and the trimmed text opens a JSON object or array. The guard stays
narrow on purpose: asset paths, object-reference handles, enum names and
char values never start with '{' or '[', and a parse failure falls back to
the original token so the existing validation error survives.
The vector-family readers additionally accept a flat numeric array next to
the canonical member object, which is what makes "[1,2,3]" reach a Vector3
at all. Bounds/BoundsInt stay object-only — their nested center/size take
the array form instead of inventing a flat six-element convention.
Closes #176
Every deferred command — test runs, package operations, profiler sampling, recording, qa run-sequence — advances one step per EditorApplication.update tick, and an idle editor runs that loop at roughly 6 ticks/second. Forcing the internal EditorApplication.SignalTick schedules the next tick immediately. Activation is automatic and scoped: deferred flows now subscribe their poll through EditorTickPump.Add/Remove instead of EditorApplication.update, and the pump runs only while at least one is subscribed. Because the subscriber set holds the very delegates that are on EditorApplication.update, a pump leak would be a poll leak — an invariant those flows already depend on — and Remove is idempotent to match their defensive double-unsubscribes. SignalTick is bound reflectively once; if it is gone the pump degrades to a no-op with a single warning rather than taking commands down with it. Measured on Unity 6000.3.10f1 / macOS with 60 update ticks: 9.4-10.4s without the pump, 6.0-6.1s with it (~1.55x), the same focused or unfocused — on that version focus barely changes the edit-mode update rate, so this is a general speed-up rather than an unfocused-only fix. Interval 0 and 16 measure identically there, so the default keeps the 16ms (~60Hz) throttle to cap the forced rate on setups whose loop can run faster. Implements the auto-activation half of #173; the explicit wire command and its protocol bump are not included.
… time The accept loops had no retry of their own: the Unix loop ends permanently on any unexpected AcceptAsync failure, and both loops give up if the very first listener acquire fails. The registry heartbeat kept publishing the instance regardless, so the CLI would resolve a live target and then fail to connect to it, over and over. BridgeHost now clears listener readiness when an accept loop ends and runs ListenerWatchdogPolicy on every editor tick. The policy is Unity-free and unit-tested: it skips while a bind is in flight or the editor is compiling/updating (domain-reload teardown is not a fault), rebinds in place up to 5 times at 5s intervals, and on exhaustion tells the host to unregister so the instance stops advertising as reachable. Recovery re-publishes the registry entry and token sidecar, deleting the previous sidecar if the rebind settled on a different hash. The auth check — the bridge's only authentication gate — moves off string.Equals, whose length test and early exit are observable to other local processes, onto a fixed-time compare. Hand-rolled rather than CryptographicOperations.FixedTimeEquals so the shared protocol source keeps compiling on both Unity's runtime profile and .NET 9. Accept/reject behavior is unchanged. Closes #174 Closes #175
CHANGELOG (both copies), CLAUDE.md architecture/conventions, README conventions, and the operator skill in both its maintainer and shipped copies.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#175+#174+#176+ the auto-activation half of#173.What changed
#175 — fixed-time auth token compare.
string.Equalson the token path (length test + early exit) is replaced byAuthTokenComparison.FixedTimeEquals. Hand-rolled rather thanCryptographicOperations.FixedTimeEqualsso the shared protocol source keeps compiling on Unity's runtime profile and .NET 9. Accept/reject behavior unchanged.#174 — listener watchdog. The Unix accept loop ended permanently on any unexpected
AcceptAsyncfailure and both loops gave up if the first acquire failed, while the heartbeat kept advertising the instance — a target the CLI resolves and then cannot connect to.BridgeHostnow clears listener readiness when an accept loop ends and runsListenerWatchdogPolicy(Unity-free, unit-tested) each editor tick: skip while a bind is in flight or the editor is compiling/updating, rebind in place up to 5× at 5 s, re-publish registry + token sidecar on recovery, and unregister the instance once the attempts are spent.#176 — agent value coercion.
ApplyTokenre-parses a string token when the target cannot hold a raw string and the trimmed text opens{or[. Vector-family readers also accept a flat numeric array, which is what lets"[1,2,3]"actually reach aVector3.#173 (half) — auto-tick. Deferred flows subscribe their poll through
EditorTickPump.Add/Removeinstead ofEditorApplication.update; the pump forcesEditorApplication.SignalTickwhile any is subscribed and disarms when the set empties. The expliciteditor auto-tickwire command and its protocol bump are not in this PR, so the wire protocol is untouched.Measurements
Auto-tick, Unity 6000.3.10f1 / macOS GUI, 60
EditorApplication.updateticks viaprofile stats --frames 60:Two things worth flagging against the issue's premise:
Application.runInBackground = true, andInteractionMode = NoThrottlingall land on exactly 100 ms. Interval 0 and 16 measure identically, so the default keeps the 16 ms throttle to cap the forced rate where the loop can run faster.Verification
dotnet test: 1032 passed. Doc-gen--checkandcheck-unity-meta.shclean.Live, against a Unity 6000.3.10f1 GUI editor with a
file:reference to this package:UNAUTHORIZED; valid succeeds again.listener가 죽어 재바인딩을 시도합니다 (1/5).then재바인딩 완료; subsequent commands succeed and the registry entry stays intact.prefab patch set-component-valueson aBoxCollider:"[1,2,3]","{\"x\":4,...}",[7,8,9]and{"x":10,...}all land. Malformed"[1,2,"still returns the originalPREFAB_FIELD_INVALID: object 값이 필요합니다; wrong arity reports it; primitives unchanged.package list,test list,profile memory,profile statsall still complete, and the pump reportsIsPumping=False SubscriberCount=0once they finish.Not covered
A whole
valuesblob sent as one quoted string ("values": "{\"m_Mass\":0.17}") is still rejected — the coercion is per-field. TheJObject-typed spec models would have to becomeJTokenfirst; happy to follow up if that shape shows up in practice.🤖 Generated with Claude Code