diff --git a/cli/common/errors/busy_status.go b/cli/common/errors/busy_status.go index 041e0ce759..ce0949c939 100644 --- a/cli/common/errors/busy_status.go +++ b/cli/common/errors/busy_status.go @@ -1,80 +1,9 @@ package clierrors import ( - "encoding/json" "fmt" - "io" ) -const cliStatusBusy = "Busy" - -type cliStatusEnvelope struct { - Status string `json:"Status"` - Message string `json:"Message"` - RunningToolName string `json:"RunningToolName,omitempty"` - RequestedToolName string `json:"RequestedToolName,omitempty"` - IsPlaying *bool `json:"IsPlaying,omitempty"` - IsPaused *bool `json:"IsPaused,omitempty"` -} - -type serverBusyStatusDetails struct { - runningToolName string - requestedToolName string - isPlaying bool - hasIsPlaying bool - isPaused bool - hasIsPaused bool -} - -func writeBusyStatusEnvelope(writer io.Writer, message string, details serverBusyStatusDetails) { - encoder := json.NewEncoder(writer) - encoder.SetIndent("", " ") - _ = encoder.Encode(cliStatusEnvelope{ - Status: cliStatusBusy, - Message: message, - RunningToolName: details.runningToolName, - RequestedToolName: details.requestedToolName, - IsPlaying: optionalBool(details.hasIsPlaying, details.isPlaying), - IsPaused: optionalBool(details.hasIsPaused, details.isPaused), - }) -} - -func serverBusyStatusDetailsFromError(err CLIError) serverBusyStatusDetails { - data, ok := serverBusyDetailsData(err.Details) - if !ok { - return serverBusyStatusDetails{ - requestedToolName: err.Command, - } - } - - isPlaying, hasIsPlaying := rpcBoolData(data, "isPlaying") - isPaused, hasIsPaused := rpcBoolData(data, "isPaused") - return serverBusyStatusDetails{ - runningToolName: rpcStringData(data, "runningToolName"), - requestedToolName: firstNonEmpty(rpcStringData(data, "requestedToolName"), err.Command), - isPlaying: isPlaying, - hasIsPlaying: hasIsPlaying, - isPaused: isPaused, - hasIsPaused: hasIsPaused, - } -} - -func serverBusyDetailsData(details map[string]any) (map[string]any, bool) { - data, ok := details["Data"].(map[string]any) - if ok { - return data, true - } - data, ok = details["data"].(map[string]any) - return data, ok -} - -func optionalBool(hasValue bool, value bool) *bool { - if !hasValue { - return nil - } - return &value -} - func unityServerBusyMessage(fallback string, data map[string]any, requestedCommand string) string { runningToolName := rpcStringData(data, "runningToolName") requestedToolName := firstNonEmpty(rpcStringData(data, "requestedToolName"), requestedCommand) @@ -97,11 +26,3 @@ func rpcStringData(data map[string]any, key string) string { } return value } - -func rpcBoolData(data map[string]any, key string) (bool, bool) { - value, ok := data[key].(bool) - if !ok { - return false, false - } - return value, true -} diff --git a/cli/common/errors/error_envelope.go b/cli/common/errors/error_envelope.go index 1e3ae3a85b..d35d6393f4 100644 --- a/cli/common/errors/error_envelope.go +++ b/cli/common/errors/error_envelope.go @@ -70,10 +70,6 @@ type ErrorContext struct { } func WriteErrorEnvelope(writer io.Writer, err CLIError) { - if err.ErrorCode == errorCodeUnityServerBusy { - writeBusyStatusEnvelope(writer, err.Message, serverBusyStatusDetailsFromError(err)) - return - } encoder := json.NewEncoder(writer) encoder.SetIndent("", " ") _ = encoder.Encode(CLIErrorEnvelope{ diff --git a/cli/common/errors/error_envelope_test.go b/cli/common/errors/error_envelope_test.go index 6860f6010c..58b1aca174 100644 --- a/cli/common/errors/error_envelope_test.go +++ b/cli/common/errors/error_envelope_test.go @@ -267,8 +267,8 @@ func TestClassifyServerBusyRPCError(t *testing.T) { } } -func TestWriteClassifiedServerBusyRPCErrorWritesBusyStatus(t *testing.T) { - // Verifies server_busy output avoids the full error envelope because busy is a temporary state. +func TestWriteClassifiedServerBusyRPCErrorWritesErrorEnvelope(t *testing.T) { + // Verifies server_busy output uses the same machine-readable error envelope as other failures. err := &unityipc.RPCError{ Code: -32603, Message: "Unity is busy running 'compile'. Retry 'get-logs' after the running tool completes.", @@ -279,33 +279,31 @@ func TestWriteClassifiedServerBusyRPCErrorWritesBusyStatus(t *testing.T) { WriteClassifiedError(&stderr, err, ErrorContext{ProjectRoot: "/tmp/MyProject", Command: "get-logs"}) - var envelope cliStatusEnvelope + var envelope CLIErrorEnvelope if err := json.Unmarshal(stderr.Bytes(), &envelope); err != nil { t.Fatalf("stderr is not valid JSON: %v\n%s", err, stderr.String()) } - if envelope.Status != cliStatusBusy { - t.Fatalf("status mismatch: %#v", envelope) + if envelope.Success { + t.Fatalf("busy envelope reported success: %#v", envelope) + } + if envelope.Error.ErrorCode != errorCodeUnityServerBusy { + t.Fatalf("error code mismatch: %#v", envelope) } expectedMessage := "'get-logs' was not executed because Unity is busy running 'compile'. uloop is single-flight by design; never run uloop commands in parallel. The CLI already retried for up to 10 seconds, so wait for 'compile' to complete and run the command again." - if envelope.Message != expectedMessage { + if envelope.Error.Message != expectedMessage { t.Fatalf("message mismatch: %#v", envelope) } - if envelope.RunningToolName != "compile" || envelope.RequestedToolName != "get-logs" { - t.Fatalf("tool names mismatch: %#v", envelope) - } - if envelope.IsPlaying == nil || *envelope.IsPlaying != true { - t.Fatalf("isPlaying mismatch: %#v", envelope) - } - if envelope.IsPaused == nil || *envelope.IsPaused != true { - t.Fatalf("isPaused mismatch: %#v", envelope) + data, ok := envelope.Error.Details["Data"].(map[string]any) + if !ok { + t.Fatalf("busy data missing: %#v", envelope) } - if bytes.Contains(stderr.Bytes(), []byte("Success")) || bytes.Contains(stderr.Bytes(), []byte("errorCode")) { - t.Fatalf("busy output should not include error envelope fields: %s", stderr.String()) + if data["runningToolName"] != "compile" || data["requestedToolName"] != "get-logs" { + t.Fatalf("tool names mismatch: %#v", data) } } -func TestWriteErrorEnvelopeServerBusyAcceptsLegacyLowercaseDataDetails(t *testing.T) { - // Verifies legacy lower-camel error details still preserve busy tool names in status output. +func TestWriteErrorEnvelopeServerBusyUsesUnifiedEnvelopeForLegacyLowercaseDataDetails(t *testing.T) { + // Verifies legacy lower-camel busy details no longer switch to the old status schema. cliErr := CLIError{ ErrorCode: errorCodeUnityServerBusy, Message: "Unity is busy.", @@ -323,18 +321,18 @@ func TestWriteErrorEnvelopeServerBusyAcceptsLegacyLowercaseDataDetails(t *testin WriteErrorEnvelope(&stderr, cliErr) - var envelope cliStatusEnvelope + var envelope CLIErrorEnvelope if err := json.Unmarshal(stderr.Bytes(), &envelope); err != nil { t.Fatalf("stderr is not valid JSON: %v\n%s", err, stderr.String()) } - if envelope.RunningToolName != "compile" || envelope.RequestedToolName != "get-logs" { - t.Fatalf("tool names mismatch: %#v", envelope) + if envelope.Success { + t.Fatalf("busy envelope reported success: %#v", envelope) } - if envelope.IsPlaying == nil || *envelope.IsPlaying != true { - t.Fatalf("isPlaying mismatch: %#v", envelope) + if envelope.Error.ErrorCode != errorCodeUnityServerBusy { + t.Fatalf("error code mismatch: %#v", envelope) } - if envelope.IsPaused == nil || *envelope.IsPaused != false { - t.Fatalf("isPaused mismatch: %#v", envelope) + if bytes.Contains(stderr.Bytes(), []byte(`"Status"`)) { + t.Fatalf("busy output should not use status schema: %s", stderr.String()) } } diff --git a/cli/dispatcher/shared-inputs-stamp.json b/cli/dispatcher/shared-inputs-stamp.json index 988efd1d91..678ad5d6d4 100644 --- a/cli/dispatcher/shared-inputs-stamp.json +++ b/cli/dispatcher/shared-inputs-stamp.json @@ -1,4 +1,4 @@ { "schemaVersion": 1, - "sharedInputsHash": "231c199b4cdb5343ecda216098befab6bf29737e" + "sharedInputsHash": "058dfc6153eecc5bd339def577253f792fa486de" } diff --git a/cli/project-runner/shared-inputs-stamp.json b/cli/project-runner/shared-inputs-stamp.json index 8dc4d4b7b8..84d13ace91 100644 --- a/cli/project-runner/shared-inputs-stamp.json +++ b/cli/project-runner/shared-inputs-stamp.json @@ -1,4 +1,4 @@ { "schemaVersion": 1, - "sharedInputsHash": "6c9ceaf4baea6b1ed24fcd2a493f7d977da58e87" + "sharedInputsHash": "8a15e5515c0c9df68396cc71e34c4df10d3f8546" }