diff --git a/Assets/Tests/Editor/JsonRpcAcceptedRequestCancellationPolicyTests.cs b/Assets/Tests/Editor/JsonRpcAcceptedRequestCancellationPolicyTests.cs
new file mode 100644
index 0000000000..34819437c1
--- /dev/null
+++ b/Assets/Tests/Editor/JsonRpcAcceptedRequestCancellationPolicyTests.cs
@@ -0,0 +1,57 @@
+using NUnit.Framework;
+
+using io.github.hatayama.UnityCliLoop.Domain;
+using io.github.hatayama.UnityCliLoop.ToolContracts;
+
+namespace io.github.hatayama.UnityCliLoop.Tests.Editor
+{
+ ///
+ /// Tests accepted-request cancellation decisions before infrastructure parses transport details.
+ ///
+ public sealed class JsonRpcAcceptedRequestCancellationPolicyTests
+ {
+ [Test]
+ public void ShouldCancelOnClientDisconnect_WhenMethodIsNotCompile_ReturnsTrue()
+ {
+ // Verifies non-compile accepted work is tied to the CLI connection lifetime.
+ bool shouldCancel = JsonRpcAcceptedRequestCancellationPolicy.ShouldCancelOnClientDisconnect(
+ UnityCliLoopConstants.TOOL_NAME_GET_LOGS,
+ true);
+
+ Assert.That(shouldCancel, Is.True);
+ }
+
+ [Test]
+ public void ShouldCancelOnClientDisconnect_WhenCompileWaitsForDomainReload_ReturnsFalse()
+ {
+ // Verifies long compile requests can persist after the CLI connection closes.
+ bool shouldCancel = JsonRpcAcceptedRequestCancellationPolicy.ShouldCancelOnClientDisconnect(
+ UnityCliLoopConstants.TOOL_NAME_COMPILE,
+ true);
+
+ Assert.That(shouldCancel, Is.False);
+ }
+
+ [Test]
+ public void ShouldCancelOnClientDisconnect_WhenCompileDoesNotWaitForDomainReload_ReturnsTrue()
+ {
+ // Verifies fire-and-forget compile requests keep the usual disconnect cancellation behavior.
+ bool shouldCancel = JsonRpcAcceptedRequestCancellationPolicy.ShouldCancelOnClientDisconnect(
+ UnityCliLoopConstants.TOOL_NAME_COMPILE,
+ false);
+
+ Assert.That(shouldCancel, Is.True);
+ }
+
+ [Test]
+ public void ShouldCancelOnClientDisconnect_WhenCompileWaitPreferenceIsUnknown_ReturnsFalse()
+ {
+ // Verifies missing, null, or non-boolean transport values preserve the compile default wait contract.
+ bool shouldCancel = JsonRpcAcceptedRequestCancellationPolicy.ShouldCancelOnClientDisconnect(
+ UnityCliLoopConstants.TOOL_NAME_COMPILE,
+ null);
+
+ Assert.That(shouldCancel, Is.False);
+ }
+ }
+}
diff --git a/Assets/Tests/Editor/JsonRpcAcceptedRequestCancellationPolicyTests.cs.meta b/Assets/Tests/Editor/JsonRpcAcceptedRequestCancellationPolicyTests.cs.meta
new file mode 100644
index 0000000000..8f6ec7f1e2
--- /dev/null
+++ b/Assets/Tests/Editor/JsonRpcAcceptedRequestCancellationPolicyTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7416db6e9cbd47a4b9b0c866195774e4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Tests/Editor/JsonRpcProcessorCliVersionGateTests.cs b/Assets/Tests/Editor/JsonRpcProcessorCliVersionGateTests.cs
index 136e155370..da87601c07 100644
--- a/Assets/Tests/Editor/JsonRpcProcessorCliVersionGateTests.cs
+++ b/Assets/Tests/Editor/JsonRpcProcessorCliVersionGateTests.cs
@@ -356,6 +356,46 @@ public async Task ProcessRequest_WhenCompileOmitsReloadWait_KeepsAcceptedRequest
}
}
+ [Test]
+ public async Task ProcessRequest_WhenCompileReloadWaitIsNotBoolean_KeepsAcceptedRequestAliveAfterDisconnect()
+ {
+ // Verifies malformed compile reload-wait params preserve the default wait contract.
+ UnityCliLoopToolRegistrarService previousService = ApplicationRegistrar.Service;
+ IToolSettingsPort toolSettingsPort = new ToolSettingsRepository();
+ UnityCliLoopToolRegistrarService service = new(
+ new EmptyInternalToolNameProvider(),
+ toolSettingsPort,
+ new UnityCliLoopToolExecutionService(new NoOpEditorRuntimeStatePort()),
+ UnityCliLoopToolDiscovery.DiscoverTools);
+ ApplicationRegistrar.RegisterService(service);
+ service.RegisterCustomTool(new CompileDispatchPolicyTestTool());
+
+ bool cancelOnClientDisconnect = true;
+ try
+ {
+ string response = await JsonRpcProcessor.ProcessRequestWithEarlyResponseAsync(
+ BuildToolRequestWithParams(
+ UnityCliLoopConstants.TOOL_NAME_COMPILE,
+ "{\"WaitForDomainReload\":\"false\"}",
+ 1),
+ CancellationToken.None,
+ (_, shouldCancelOnClientDisconnect, _) =>
+ {
+ cancelOnClientDisconnect = shouldCancelOnClientDisconnect;
+ return Task.CompletedTask;
+ });
+ JObject parsed = JObject.Parse(response);
+
+ Assert.That(parsed["error"], Is.Null);
+ Assert.That(parsed["result"], Is.Not.Null);
+ Assert.That(cancelOnClientDisconnect, Is.False);
+ }
+ finally
+ {
+ ApplicationRegistrar.RegisterService(previousService);
+ }
+ }
+
[Test]
public async Task ProcessRequest_WhenCompileDoesNotWaitForDomainReload_CancelsOnClientDisconnect()
{
diff --git a/Packages/src/Editor/Domain/JsonRpcAcceptedRequestCancellationPolicy.cs b/Packages/src/Editor/Domain/JsonRpcAcceptedRequestCancellationPolicy.cs
new file mode 100644
index 0000000000..d18ddf3e81
--- /dev/null
+++ b/Packages/src/Editor/Domain/JsonRpcAcceptedRequestCancellationPolicy.cs
@@ -0,0 +1,27 @@
+using io.github.hatayama.UnityCliLoop.ToolContracts;
+
+namespace io.github.hatayama.UnityCliLoop.Domain
+{
+ ///
+ /// Decides whether accepted JSON-RPC work should be canceled when the CLI disconnects.
+ ///
+ public static class JsonRpcAcceptedRequestCancellationPolicy
+ {
+ public static bool ShouldCancelOnClientDisconnect(
+ string methodName,
+ bool? compileWaitsForDomainReload)
+ {
+ if (methodName != UnityCliLoopConstants.TOOL_NAME_COMPILE)
+ {
+ return true;
+ }
+
+ return !CompileRequestWaitsForDomainReload(compileWaitsForDomainReload);
+ }
+
+ private static bool CompileRequestWaitsForDomainReload(bool? compileWaitsForDomainReload)
+ {
+ return compileWaitsForDomainReload ?? true;
+ }
+ }
+}
diff --git a/Packages/src/Editor/Domain/JsonRpcAcceptedRequestCancellationPolicy.cs.meta b/Packages/src/Editor/Domain/JsonRpcAcceptedRequestCancellationPolicy.cs.meta
new file mode 100644
index 0000000000..9a6d35c270
--- /dev/null
+++ b/Packages/src/Editor/Domain/JsonRpcAcceptedRequestCancellationPolicy.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4a6bb389836e49ddb1652ffe0cf17e65
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Packages/src/Editor/Infrastructure/Api/JsonRpcProcessor.cs b/Packages/src/Editor/Infrastructure/Api/JsonRpcProcessor.cs
index e768fb6937..e907b81a55 100644
--- a/Packages/src/Editor/Infrastructure/Api/JsonRpcProcessor.cs
+++ b/Packages/src/Editor/Infrastructure/Api/JsonRpcProcessor.cs
@@ -283,31 +283,29 @@ private static bool ShouldCancelAcceptedRequestOnClientDisconnect(JsonRpcRequest
{
System.Diagnostics.Debug.Assert(request != null, "request must not be null");
- if (request.Method != UnityCliLoopConstants.TOOL_NAME_COMPILE)
- {
- return true;
- }
-
- return !CompileRequestWaitsForDomainReload(request.Params);
+ bool? compileWaitsForDomainReload = ReadCompileRequestWaitsForDomainReload(request.Params);
+ return JsonRpcAcceptedRequestCancellationPolicy.ShouldCancelOnClientDisconnect(
+ request.Method,
+ compileWaitsForDomainReload);
}
- private static bool CompileRequestWaitsForDomainReload(JToken paramsToken)
+ private static bool? ReadCompileRequestWaitsForDomainReload(JToken paramsToken)
{
if (paramsToken is not JObject paramsObject)
{
- return true;
+ return null;
}
JToken waitForDomainReloadToken =
paramsObject.GetValue(WaitForDomainReloadParamName, StringComparison.OrdinalIgnoreCase);
if (waitForDomainReloadToken == null)
{
- return true;
+ return null;
}
if (waitForDomainReloadToken.Type != JTokenType.Boolean)
{
- return true;
+ return null;
}
return waitForDomainReloadToken.Value();